diff --git a/CHANGES b/CHANGES index 123c8aba..52ae7372 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,117 @@ +XX NNN 2012 - 2.7.0-rc1 +------------------- + + * Added SecEncryptionEngine. Initial crypt engine support, at the momment it will sign some Html + and Response Header options. + + * Added SecEncryptionKey to define the a rand or static key for crypt engine. + + * Added SecEncryptionParam to define the new parameter name. + + * Added SecEncryptionMethodRx used with a regular expression to inspect the html in response + body/header and decide what to protect. + + * Added SecEncryptionMethodPm used with multiple or single strings to inspect the html in response + body/header and decide what to protect. + + * Added ctl encryptionEngine as a per transaction version of SecEncryptionEgine diretive. + + * Added ctl encryptionEnforcement that will allow the engine to sign the data but the enforcement is + disabled. + + * Added validateEncryption operator to enforce the signed elements. + + * Added rsub operator supports the syntax |hex| allowing users to use special chars like \n \r. + + * Added SecRuleUpdateTargetById now supports id range. + + * Added SecRuleUpdateTargetByMsg and its ctl version (Thanks Scott Gifford). + + * Added SecRuleUpdateTargetByTag and its ctl version (Thanks Scott Gifford). + + * Added SecRulePerfTime when greater than zero it will fill rule id's execution time into PERF_RULE + and log id=usec information in the new Perf-rule-info: line in part H. + + * Added PERF_RULES variable that contains rule execution time. + + * Added Engine-mode: section in part H. + + * Added ruleRemoveByMsg ctl version. + + * Added removeCommentsChar and removeComments now can work with style. + + * Added SecArgumentSeparator and SecCookieFormat can be used in different scope locations. + + * Added Rules must have ID action and must be numeric. + + * Added The use of tfns are deprecated in SecDefaultAction. Should be forbid in the future. + + * Added Macro expansion support to the action pause. + + * Added IpmatchFromFile/IpmatchF operator. + + * Added New setrsc action, the RESOURCE collection used SecWebAppId Name Space + + * Added Configure option --enable-cache-lua that allows reuse of Lua VM per transaction. + It will only take any effect when ModSecurity has multiple scripts to run per transaction. + + * Added Configure option --enable-pcre-jit that allows ModSecurity regex engine to use PCRE Jit support. + + * Added Configure option --enable-request-early that allows ModSecurity run phase 1 in post_read_request hook. + + * Added RBL operator now support the httpBl api (http://www.projecthoneypot.org/httpbl_api.php). + + * Added SecHttpBlKey to be used with httpBl api. + + * Added SecSensorId will specify the modsecurity sensor name into audit log part H. + + * Added aliases to phase:2 (phase:request), phase:4 (phase:response) and phase:5 (phase:logging). + + * Added USERAGENT_IP variable. Created when Apache24 is used with mod_remoteip to know the real + client ip address. + + * Fixed Variable DURATION contains the elapsed time in microseconds for compatible reasons with apache and + other variables. + + * Fixed Preserve names/identity of the variables going into MATCHED_VARS. + + * Fixed Redirect macro expansion does not work in SecDefaultAction when SecRule uses block action. + + * Fixed rsub operator does not work as expect if regex contains parentheses (Thanks Jerome Freilinger). + + * Current Google Safe Browsing implementation is deprecated. Google changed the API and does not allow + anymore the malware database for download. + +20 Mar 2012 - 2.6.5 +------------------- + + * Fixed increased a specific message debug level in SBDM code (MODSEC-293). + + * Cleanup build system. + +09 Mar 2012 - 2.6.4 +------------------- + + * Fixed Mlogc 100% CPU consume (Thanks Klaubert Herr and Ebrahim Khalilzadeh). + + * Fixed ModSecurity cannot load session and user sdbm data. + + * Fixed updateTargetById was creating rule unparsed content making apache memory grow. + + * Code cleanup. + +23 Feb 2012 - 2.6.4-rc1 +------------------- + + * Fixed @rsub adding garbage data into stream variables. + + * Fixed regex for section A into mlogc-batch-load.pl (Thanks Ebrahim Khalilzadeh). + + * Fixed logdata cuts message without closing it with final chars. + + * Added sanitizeMatchedBytes support to verifyCPF, verifyCC and verifySSN. + + 06 Dec 2011 - 2.6.3-rc1 ------------------- @@ -29,7 +143,7 @@ * Added new transformations removeComments and removeCommentsChars - * Fixed collection names are not case-sensitive anymore. + * Fixed colletion names are not case-sensitive anymore. * Fixed compilation errors with apache 2.0. diff --git a/Makefile.am b/Makefile.am index e8fd806e..35b3797b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -26,8 +26,9 @@ MAINTAINERCLEANFILES += $(CLEANFILES) \ ext/Makefile.in \ mlogc/Makefile.in \ modsecurity_config_auto.h.in~ \ - tests/Makefile.in \ - tools/Makefile.in + config.log \ + Makefile \ + config.status # Alias for "check" test: check diff --git a/apache2/apache2_config.c b/apache2/apache2_config.c index 4a9eaa7c..8be58a5e 100644 --- a/apache2/apache2_config.c +++ b/apache2/apache2_config.c @@ -18,6 +18,9 @@ #include "msc_logging.h" #include "msc_util.h" #include "http_log.h" +#include "apr_lib.h" +#include "acmp.h" +#include "msc_crypt.h" #if defined(WITH_LUA) #include "msc_lua.h" @@ -64,10 +67,12 @@ void *create_directory_config(apr_pool_t *mp, char *path) dcfg->rule_inheritance = NOT_SET; dcfg->rule_exceptions = apr_array_make(mp, 16, sizeof(rule_exception *)); + dcfg->encryption_method = apr_array_make(mp, 16, sizeof(encryption_method *)); /* audit log variables */ dcfg->auditlog_flag = NOT_SET; dcfg->auditlog_type = NOT_SET; + dcfg->max_rule_time = NOT_SET; dcfg->auditlog_dirperms = NOT_SET; dcfg->auditlog_fileperms = NOT_SET; dcfg->auditlog_name = NOT_SET_P; @@ -96,6 +101,7 @@ void *create_directory_config(apr_pool_t *mp, char *path) /* Misc */ dcfg->data_dir = NOT_SET_P; dcfg->webappid = NOT_SET_P; + dcfg->sensor_id = NOT_SET_P; dcfg->httpBlkey = NOT_SET_P; /* Content injection. */ @@ -129,6 +135,23 @@ void *create_directory_config(apr_pool_t *mp, char *path) /* Collection timeout */ dcfg->col_timeout = NOT_SET; + dcfg->crypto_key = NOT_SET_P; + dcfg->crypto_key_add = NOT_SET; + dcfg->crypto_param_name = NOT_SET_P; + dcfg->encryption_is_enabled = NOT_SET; + dcfg->encryption_enforcement = NOT_SET; + dcfg->crypto_hash_href_rx = NOT_SET; + dcfg->crypto_hash_faction_rx = NOT_SET; + dcfg->crypto_hash_location_rx = NOT_SET; + dcfg->crypto_hash_iframesrc_rx = NOT_SET; + dcfg->crypto_hash_framesrc_rx = NOT_SET; + dcfg->crypto_hash_href_pm = NOT_SET; + dcfg->crypto_hash_faction_pm = NOT_SET; + dcfg->crypto_hash_location_pm = NOT_SET; + dcfg->crypto_hash_iframesrc_pm = NOT_SET; + dcfg->crypto_hash_framesrc_pm = NOT_SET; + + return dcfg; } @@ -422,11 +445,16 @@ void *merge_directory_configs(apr_pool_t *mp, void *_parent, void *_child) merged->rule_exceptions = apr_array_append(mp, parent->rule_exceptions, child->rule_exceptions); + merged->encryption_method = apr_array_append(mp, parent->encryption_method, + child->encryption_method); + /* audit log variables */ merged->auditlog_flag = (child->auditlog_flag == NOT_SET ? parent->auditlog_flag : child->auditlog_flag); merged->auditlog_type = (child->auditlog_type == NOT_SET ? parent->auditlog_type : child->auditlog_type); + merged->max_rule_time = (child->max_rule_time == NOT_SET + ? parent->max_rule_time : child->max_rule_time); merged->auditlog_dirperms = (child->auditlog_dirperms == NOT_SET ? parent->auditlog_dirperms : child->auditlog_dirperms); merged->auditlog_fileperms = (child->auditlog_fileperms == NOT_SET @@ -471,6 +499,8 @@ void *merge_directory_configs(apr_pool_t *mp, void *_parent, void *_child) ? parent->data_dir : child->data_dir); merged->webappid = (child->webappid == NOT_SET_P ? parent->webappid : child->webappid); + merged->sensor_id = (child->sensor_id == NOT_SET_P + ? parent->sensor_id : child->sensor_id); merged->httpBlkey = (child->httpBlkey == NOT_SET_P ? parent->httpBlkey : child->httpBlkey); @@ -521,6 +551,38 @@ void *merge_directory_configs(apr_pool_t *mp, void *_parent, void *_child) merged->col_timeout = (child->col_timeout == NOT_SET ? parent->col_timeout : child->col_timeout); + /* Encryption */ + merged->crypto_key = (child->crypto_key == NOT_SET_P + ? parent->crypto_key : child->crypto_key); + merged->crypto_key_add = (child->crypto_key_add == NOT_SET + ? parent->crypto_key_add : child->crypto_key_add); + merged->crypto_param_name = (child->crypto_param_name == NOT_SET_P + ? parent->crypto_param_name : child->crypto_param_name); + merged->encryption_is_enabled = (child->encryption_is_enabled == NOT_SET + ? parent->encryption_is_enabled : child->encryption_is_enabled); + merged->encryption_enforcement = (child->encryption_enforcement == NOT_SET + ? parent->encryption_enforcement : child->encryption_enforcement); + merged->crypto_hash_href_rx = (child->crypto_hash_href_rx == NOT_SET + ? parent->crypto_hash_href_rx : child->crypto_hash_href_rx); + merged->crypto_hash_faction_rx = (child->crypto_hash_faction_rx == NOT_SET + ? parent->crypto_hash_faction_rx : child->crypto_hash_faction_rx); + merged->crypto_hash_location_rx = (child->crypto_hash_location_rx == NOT_SET + ? parent->crypto_hash_location_rx : child->crypto_hash_location_rx); + merged->crypto_hash_iframesrc_rx = (child->crypto_hash_iframesrc_rx == NOT_SET + ? parent->crypto_hash_iframesrc_rx : child->crypto_hash_iframesrc_rx); + merged->crypto_hash_framesrc_rx = (child->crypto_hash_framesrc_rx == NOT_SET + ? parent->crypto_hash_framesrc_rx : child->crypto_hash_framesrc_rx); + merged->crypto_hash_href_pm = (child->crypto_hash_href_pm == NOT_SET + ? parent->crypto_hash_href_pm : child->crypto_hash_href_pm); + merged->crypto_hash_faction_pm = (child->crypto_hash_faction_pm == NOT_SET + ? parent->crypto_hash_faction_pm : child->crypto_hash_faction_pm); + merged->crypto_hash_location_pm = (child->crypto_hash_location_pm == NOT_SET + ? parent->crypto_hash_location_pm : child->crypto_hash_location_pm); + merged->crypto_hash_iframesrc_pm = (child->crypto_hash_iframesrc_pm == NOT_SET + ? parent->crypto_hash_iframesrc_pm : child->crypto_hash_iframesrc_pm); + merged->crypto_hash_framesrc_pm = (child->crypto_hash_framesrc_pm == NOT_SET + ? parent->crypto_hash_framesrc_pm : child->crypto_hash_framesrc_pm); + return merged; } @@ -568,6 +630,7 @@ void init_directory_config(directory_config *dcfg) /* audit log variables */ if (dcfg->auditlog_flag == NOT_SET) dcfg->auditlog_flag = 0; if (dcfg->auditlog_type == NOT_SET) dcfg->auditlog_type = AUDITLOG_SERIAL; + if (dcfg->max_rule_time == NOT_SET) dcfg->max_rule_time = 0; if (dcfg->auditlog_dirperms == NOT_SET) dcfg->auditlog_dirperms = CREATEMODE_DIR; if (dcfg->auditlog_fileperms == NOT_SET) dcfg->auditlog_fileperms = CREATEMODE; if (dcfg->auditlog_fd == NOT_SET_P) dcfg->auditlog_fd = NULL; @@ -589,6 +652,7 @@ void init_directory_config(directory_config *dcfg) /* Misc */ if (dcfg->data_dir == NOT_SET_P) dcfg->data_dir = NULL; if (dcfg->webappid == NOT_SET_P) dcfg->webappid = "default"; + if (dcfg->sensor_id == NOT_SET_P) dcfg->sensor_id = "default"; if (dcfg->httpBlkey == NOT_SET_P) dcfg->httpBlkey = NULL; /* Content injection. */ @@ -619,6 +683,24 @@ void init_directory_config(directory_config *dcfg) if (dcfg->disable_backend_compression == NOT_SET) dcfg->disable_backend_compression = 0; if (dcfg->col_timeout == NOT_SET) dcfg->col_timeout = 3600; + + /* Encryption */ + if (dcfg->crypto_key == NOT_SET_P) dcfg->crypto_key = getkey(dcfg->mp); + if (dcfg->crypto_key_add == NOT_SET) dcfg->crypto_key_add = ENCRYPTION_KEYONLY; + if (dcfg->crypto_param_name == NOT_SET_P) dcfg->crypto_param_name = "crypt"; + if (dcfg->encryption_is_enabled == NOT_SET) dcfg->encryption_is_enabled = ENCRYPTION_DISABLED; + if (dcfg->encryption_enforcement == NOT_SET) dcfg->encryption_enforcement = ENCRYPTION_DISABLED; + if (dcfg->crypto_hash_href_rx == NOT_SET) dcfg->crypto_hash_href_rx = 0; + if (dcfg->crypto_hash_faction_rx == NOT_SET) dcfg->crypto_hash_faction_rx = 0; + if (dcfg->crypto_hash_location_rx == NOT_SET) dcfg->crypto_hash_location_rx = 0; + if (dcfg->crypto_hash_iframesrc_rx == NOT_SET) dcfg->crypto_hash_iframesrc_rx = 0; + if (dcfg->crypto_hash_framesrc_rx == NOT_SET) dcfg->crypto_hash_framesrc_rx = 0; + if (dcfg->crypto_hash_href_pm == NOT_SET) dcfg->crypto_hash_href_pm = 0; + if (dcfg->crypto_hash_faction_pm == NOT_SET) dcfg->crypto_hash_faction_pm = 0; + if (dcfg->crypto_hash_location_pm == NOT_SET) dcfg->crypto_hash_location_pm = 0; + if (dcfg->crypto_hash_iframesrc_pm == NOT_SET) dcfg->crypto_hash_iframesrc_pm = 0; + if (dcfg->crypto_hash_framesrc_pm == NOT_SET) dcfg->crypto_hash_framesrc_pm = 0; + } /** @@ -628,8 +710,9 @@ static const char *add_rule(cmd_parms *cmd, directory_config *dcfg, int type, const char *p1, const char *p2, const char *p3) { char *my_error_msg = NULL; - msre_rule *rule = NULL; + msre_rule *rule = NULL, *tmp_rule = NULL; extern msc_engine *modsecurity; + int offset = 0; #ifdef DEBUG_CONF ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool, @@ -660,6 +743,32 @@ static const char *add_rule(cmd_parms *cmd, directory_config *dcfg, int type, return my_error_msg; } + /* Rules must have uniq ID */ + if ( +#if defined(WITH_LUA) + type != RULE_TYPE_LUA && +#endif + (dcfg->tmp_chain_starter == NULL)) + if(rule->actionset == NULL) + return "Rules must have at least id action"; + + if(rule->actionset != NULL && (dcfg->tmp_chain_starter == NULL)) { + if(rule->actionset->id == NOT_SET_P +#if defined(WITH_LUA) + && (type != RULE_TYPE_LUA) +#endif + ) + return "No action id present within the rule"; +#if defined(WITH_LUA) + if(type != RULE_TYPE_LUA) +#endif + { + tmp_rule = msre_ruleset_fetch_rule(dcfg->ruleset, rule->actionset->id, offset); + if(tmp_rule != NULL) + return "Found another rule with the same id"; + } + } + /* Create default actionset if one does not already exist. */ if (dcfg->tmp_default_actionset == NULL) { dcfg->tmp_default_actionset = msre_actionset_create_default(modsecurity->msre); @@ -1261,6 +1370,16 @@ static const char *cmd_debug_log(cmd_parms *cmd, void *_dcfg, const char *p1) return NULL; } +/** +* \brief Add SecCollectionTimeout 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_collection_timeout(cmd_parms *cmd, void *_dcfg, const char *p1) { @@ -1329,6 +1448,14 @@ static const char *cmd_default_action(cmd_parms *cmd, void *_dcfg, cmd->directive->filename, cmd->directive->line_num); } + if (apr_table_get(dcfg->tmp_default_actionset->actions, "t")) { + ap_log_perror(APLOG_MARK, + APLOG_STARTUP|APLOG_WARNING|APLOG_NOERRNO, 0, cmd->pool, + "ModSecurity: WARNING Using transformations in " + "SecDefaultAction is deprecated (%s:%d).", + cmd->directive->filename, cmd->directive->line_num); + } + /* Must not use chain. */ if (dcfg->tmp_default_actionset->is_chained != NOT_SET) { return apr_psprintf(cmd->pool, "ModSecurity: SecDefaultAction must not " @@ -1409,7 +1536,7 @@ static const char *cmd_guardian_log(cmd_parms *cmd, void *_dcfg, return NULL; } -/* +/** * \brief Add SecStreamInBodyInspection configuration option * * \param cmd Pointer to configuration data @@ -1428,7 +1555,7 @@ static const char *cmd_stream_inbody_inspection(cmd_parms *cmd, void *_dcfg, int } -/* +/** * \brief Add SecStreamOutBodyInspection configuration option * * \param cmd Pointer to configuration data @@ -1445,8 +1572,35 @@ static const char *cmd_stream_outbody_inspection(cmd_parms *cmd, void *_dcfg, in dcfg->stream_outbody_inspection = flag; return NULL; } +/** +* \brief Add SecRulePerfTime 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_rule_perf_time(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 SecRulePerfTime: %s", p1); + } + + dcfg->max_rule_time = limit; + + return NULL; +} + +/** * \brief Add SecReadStateLimit configuration option * * \param cmd Pointer to configuration data @@ -1474,7 +1628,7 @@ static const char *cmd_conn_read_state_limit(cmd_parms *cmd, void *_dcfg, return NULL; } -/* +/** * \brief Add SecWriteStateLimit configuration option * * \param cmd Pointer to configuration data @@ -1572,6 +1726,16 @@ static const char *cmd_request_body_access(cmd_parms *cmd, void *_dcfg, return NULL; } +/** +* \brief Add SecInterceptOnError 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_request_intercept_on_error(cmd_parms *cmd, void *_dcfg, const char *p1) { @@ -1656,6 +1820,16 @@ static const char *cmd_response_body_limit_action(cmd_parms *cmd, void *_dcfg, return NULL; } +/** +* \brief Add SecRequestBodyLimitAction 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_resquest_body_limit_action(cmd_parms *cmd, void *_dcfg, const char *p1) { @@ -1709,23 +1883,107 @@ static const char *cmd_response_body_mime_types_clear(cmd_parms *cmd, return NULL; } -/* -* \brief Add SecRuleUpdateTargetById -* -* \param cmd Pointer to configuration data -* \param _dcfg Pointer to directory configuration -* \param p1 Pointer to configuration option -* \param p2 Pointer to configuration option -* \param p3 Pointer to configuration option -* -* \retval NULL On failure|Success -*/ +/** + * \brief Add SecRuleUpdateTargetById + * + * \param cmd Pointer to configuration data + * \param _dcfg Pointer to directory configuration + * \param p1 Pointer to configuration option + * \param p2 Pointer to configuration option + * \param p3 Pointer to configuration option + * + * \retval NULL On failure|Success + */ static const char *cmd_rule_update_target_by_id(cmd_parms *cmd, void *_dcfg, const char *p1, const char *p2, const char *p3) { - return update_rule_target(cmd, (directory_config *)_dcfg, NULL, p1, p2, p3); + directory_config *dcfg = (directory_config *)_dcfg; + rule_exception *re = apr_pcalloc(cmd->pool, sizeof(rule_exception)); + if (dcfg == NULL) return NULL; + + if(p1 == NULL) { + return apr_psprintf(cmd->pool, "Updating target by ID with no ID"); + } + + re->type = RULE_EXCEPTION_REMOVE_ID; + /* TODO: Validate the range here, while we can still tell the user if it's invalid */ + re->param = p1; + + return msre_ruleset_rule_update_target_matching_exception(NULL, dcfg->ruleset, re, p2, p3); } +/** + * \brief Add SecRuleUpdateTargetByTag configuration option + * + * \param cmd Pointer to configuration data + * \param _dcfg Pointer to directory configuration + * \param p1 Pointer to configuration option RULETAG + * \param p2 Pointer to configuration option TARGET + * \param p3 Pointer to configuration option REPLACED_TARGET + * \todo Finish documenting + * + * \retval NULL On success + * \retval apr_psprintf On failure + * + * \todo Figure out error checking + */ +static const char *cmd_rule_update_target_by_tag(cmd_parms *cmd, void *_dcfg, + const char *p1, const char *p2, const char *p3) +{ + directory_config *dcfg = (directory_config *)_dcfg; + rule_exception *re = apr_pcalloc(cmd->pool, sizeof(rule_exception)); + if (dcfg == NULL) return NULL; + + if(p1 == NULL) { + return apr_psprintf(cmd->pool, "Updating target by tag with no tag"); + } + + re->type = RULE_EXCEPTION_REMOVE_TAG; + re->param = p1; + re->param_data = msc_pregcomp(cmd->pool, p1, 0, NULL, NULL); + if (re->param_data == NULL) { + return apr_psprintf(cmd->pool, "ModSecurity: Invalid regular expression: %s", p1); + } + + return msre_ruleset_rule_update_target_matching_exception(NULL, dcfg->ruleset, re, p2, p3); +} +/** + * \brief Add SecRuleUpdateTargetByMsg configuration option + * + * \param cmd Pointer to configuration data + * \param _dcfg Pointer to directory configuration + * \param p1 Pointer to configuration option RULEMSG + * \param p2 Pointer to configuration option TARGET + * \param p3 Pointer to configuration option REPLACED_TARGET + * \todo Finish documenting + * + * \retval NULL On success + * \retval apr_psprintf On failure + * + * \todo Figure out error checking + */ +static const char *cmd_rule_update_target_by_msg(cmd_parms *cmd, void *_dcfg, + const char *p1, const char *p2, const char *p3) +{ + directory_config *dcfg = (directory_config *)_dcfg; + rule_exception *re = apr_pcalloc(cmd->pool, sizeof(rule_exception)); + if (dcfg == NULL) return NULL; + + if(p1 == NULL) { + return apr_psprintf(cmd->pool, "Updating target by message with no message"); + } + + re->type = RULE_EXCEPTION_REMOVE_MSG; + re->param = p1; + re->param_data = msc_pregcomp(cmd->pool, p1, 0, NULL, NULL); + if (re->param_data == NULL) { + return apr_psprintf(cmd->pool, "ModSecurity: Invalid regular expression: %s", p1); + } + + return msre_ruleset_rule_update_target_matching_exception(NULL, dcfg->ruleset, re, p2, p3); +} + + static const char *cmd_rule(cmd_parms *cmd, void *_dcfg, const char *p1, const char *p2, const char *p3) { @@ -1788,6 +2046,16 @@ static const char *cmd_rule_remove_by_id(cmd_parms *cmd, void *_dcfg, return NULL; } +/** +* \brief Add SecRuleRemoveByTag 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_rule_remove_by_tag(cmd_parms *cmd, void *_dcfg, const char *p1) { @@ -1965,6 +2233,277 @@ static const char *cmd_web_app_id(cmd_parms *cmd, void *_dcfg, const char *p1) return NULL; } +static const char *cmd_sensor_id(cmd_parms *cmd, void *_dcfg, const char *p1) +{ + directory_config *dcfg = (directory_config *)_dcfg; + + /* ENH enforce format (letters, digits, ., _, -) */ + dcfg->sensor_id = p1; + + return NULL; +} + + +/** +* \brief Add SecEncryption 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_encryption_engine(cmd_parms *cmd, void *_dcfg, const char *p1) +{ + directory_config *dcfg = (directory_config *)_dcfg; + if (dcfg == NULL) return NULL; + + if (strcasecmp(p1, "on") == 0) { + dcfg->encryption_is_enabled = ENCRYPTION_ENABLED; + dcfg->encryption_enforcement = ENCRYPTION_ENABLED; + } + else if (strcasecmp(p1, "off") == 0) { + dcfg->encryption_is_enabled = ENCRYPTION_DISABLED; + dcfg->encryption_enforcement = ENCRYPTION_DISABLED; + } + else return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecRuleEngine: %s", p1); + + return NULL; +} + +/** +* \brief Add SecEncryptionPram configuration option +* +* \param cmd Pointer to configuration data +* \param _dcfg Pointer to directory configuration +* \param p1 Pointer to configuration option +* +* \retval NULL On success +*/ +static const char *cmd_encryption_param(cmd_parms *cmd, void *_dcfg, const char *p1) +{ + directory_config *dcfg = (directory_config *)_dcfg; + + if (dcfg == NULL) return NULL; + + if (p1 == NULL) return NULL; + dcfg->crypto_param_name = p1; + + return NULL; +} + +/** +* \brief Add SecEncryptionKey configuration option +* +* \param cmd Pointer to configuration data +* \param _dcfg Pointer to directory configuration +* \param _p1 Pointer to configuration option +* \param _p2 Pointer to configuration option +* +* \retval NULL On success +*/ +static const char *cmd_encryption_key(cmd_parms *cmd, void *_dcfg, const char *_p1, const char *_p2) +{ + directory_config *dcfg = (directory_config *)_dcfg; + char *p1 = NULL; + + if (dcfg == NULL) return NULL; + + if (p1 == NULL) return NULL; + if (strcasecmp(p1, "Rand") == 0) { + p1 = apr_pstrdup(cmd->pool, getkey(cmd->pool)); + dcfg->crypto_key = p1; + dcfg->crypto_key_len = strlen(dcfg->crypto_key); + } else { + p1 = apr_pstrdup(cmd->pool, _p1); + dcfg->crypto_key = p1; + dcfg->crypto_key_len = strlen(p1); + } + + if(_p2 == NULL) { + return NULL; + } else { + if (strcasecmp(_p2, "KeyOnly") == 0) + dcfg->crypto_key_add = ENCRYPTION_KEYONLY; + else if (strcasecmp(_p2, "SessionID") == 0) + dcfg->crypto_key_add = ENCRYPTION_SESSIONID; + else if (strcasecmp(_p2, "RemoteIP") == 0) + dcfg->crypto_key_add = ENCRYPTION_REMOTEIP; + } + return NULL; +} + +/** +* \brief Add SecEncryptionMethodPm configuration option +* +* \param cmd Pointer to configuration data +* \param _dcfg Pointer to directory configuration +* \param p1 Pointer to configuration option +* \param p2 Pointer to configuration option +* +* \retval NULL On failure +* \retval apr_psprintf On Success +*/ +static const char *cmd_encryption_method_pm(cmd_parms *cmd, void *_dcfg, + const char *p1, const char *p2) +{ + directory_config *dcfg = (directory_config *)_dcfg; + rule_exception *re = apr_pcalloc(cmd->pool, sizeof(encryption_method)); + const char *_p2 = apr_pstrdup(cmd->pool, p2); + ACMP *p = NULL; + const char *phrase = NULL; + const char *next = NULL; + + if (dcfg == NULL) return NULL; + + p = acmp_create(0, cmd->pool); + if (p == NULL) return NULL; + + if(phrase == NULL) + phrase = apr_pstrdup(cmd->pool, _p2); + + for (;;) { + while((apr_isspace(*phrase) != 0) && (*phrase != '\0')) phrase++; + if (*phrase == '\0') break; + next = phrase; + while((apr_isspace(*next) == 0) && (*next != 0)) next++; + acmp_add_pattern(p, phrase, NULL, NULL, next - phrase); + phrase = next; + } + + acmp_prepare(p); + + if (strcasecmp(p1, "HashHref") == 0) { + re->type = ENCRYPTION_URL_HREF_HASH_PM; + re->param = _p2; + re->param_data = (void *)p; + if (re->param_data == NULL) { + return apr_psprintf(cmd->pool, "ModSecurity: Invalid pattern: %s", p2); + } + dcfg->crypto_hash_href_pm = 1; + } + else if (strcasecmp(p1, "HashFormAction") == 0) { + re->type = ENCRYPTION_URL_FACTION_HASH_PM; + re->param = _p2; + re->param_data = (void *)p; + if (re->param_data == NULL) { + return apr_psprintf(cmd->pool, "ModSecurity: Invalid pattern: %s", p2); + } + dcfg->crypto_hash_faction_pm = 1; + } + else if (strcasecmp(p1, "HashLocation") == 0) { + re->type = ENCRYPTION_URL_LOCATION_HASH_PM; + re->param = _p2; + re->param_data = (void *)p; + if (re->param_data == NULL) { + return apr_psprintf(cmd->pool, "ModSecurity: Invalid pattern: %s", p2); + } + dcfg->crypto_hash_location_pm = 1; + } + else if (strcasecmp(p1, "HashIframeSrc") == 0) { + re->type = ENCRYPTION_URL_IFRAMESRC_HASH_PM; + re->param = _p2; + re->param_data = (void *)p; + if (re->param_data == NULL) { + return apr_psprintf(cmd->pool, "ModSecurity: Invalid pattern: %s", p2); + } + dcfg->crypto_hash_iframesrc_pm = 1; + } + else if (strcasecmp(p1, "HashFrameSrc") == 0) { + re->type = ENCRYPTION_URL_FRAMESRC_HASH_PM; + re->param = _p2; + re->param_data = (void *)p; + if (re->param_data == NULL) { + return apr_psprintf(cmd->pool, "ModSecurity: Invalid pattern: %s", p2); + } + dcfg->crypto_hash_framesrc_pm = 1; + } + + *(encryption_method **)apr_array_push(dcfg->encryption_method) = re; + + return NULL; +} + +/** + * \brief Add SecEncryptionMethodRx configuration option + * + * \param cmd Pointer to configuration data + * \param _dcfg Pointer to directory configuration + * \param p1 Pointer to configuration option + * \param p2 Pointer to configuration option + * + * \retval NULL On failure + * \retval apr_psprintf On Success + */ +static const char *cmd_encryption_method_rx(cmd_parms *cmd, void *_dcfg, + const char *p1, const char *p2) +{ + directory_config *dcfg = (directory_config *)_dcfg; + rule_exception *re = apr_pcalloc(cmd->pool, sizeof(encryption_method)); + const char *_p2 = apr_pstrdup(cmd->pool, p2); + if (dcfg == NULL) return NULL; + + if (strcasecmp(p1, "HashHref") == 0) { + re->type = ENCRYPTION_URL_HREF_HASH_RX; + re->param = _p2; + re->param_data = msc_pregcomp(cmd->pool, p2, 0, NULL, NULL); + if (re->param_data == NULL) { + return apr_psprintf(cmd->pool, "ModSecurity: Invalid regular expression: %s", p2); + } + dcfg->crypto_hash_href_rx = 1; + } + else if (strcasecmp(p1, "HashFormAction") == 0) { + re->type = ENCRYPTION_URL_FACTION_HASH_RX; + re->param = _p2; + re->param_data = msc_pregcomp(cmd->pool, p2, 0, NULL, NULL); + if (re->param_data == NULL) { + return apr_psprintf(cmd->pool, "ModSecurity: Invalid regular expression: %s", p2); + } + dcfg->crypto_hash_faction_rx = 1; + } + else if (strcasecmp(p1, "HashLocation") == 0) { + re->type = ENCRYPTION_URL_LOCATION_HASH_RX; + re->param = _p2; + re->param_data = msc_pregcomp(cmd->pool, p2, 0, NULL, NULL); + if (re->param_data == NULL) { + return apr_psprintf(cmd->pool, "ModSecurity: Invalid regular expression: %s", p2); + } + dcfg->crypto_hash_location_rx = 1; + } + else if (strcasecmp(p1, "HashIframeSrc") == 0) { + re->type = ENCRYPTION_URL_IFRAMESRC_HASH_RX; + re->param = _p2; + re->param_data = msc_pregcomp(cmd->pool, p2, 0, NULL, NULL); + if (re->param_data == NULL) { + return apr_psprintf(cmd->pool, "ModSecurity: Invalid regular expression: %s", p2); + } + dcfg->crypto_hash_iframesrc_rx = 1; + } + else if (strcasecmp(p1, "HashFrameSrc") == 0) { + re->type = ENCRYPTION_URL_FRAMESRC_HASH_RX; + re->param = _p2; + re->param_data = msc_pregcomp(cmd->pool, p2, 0, NULL, NULL); + if (re->param_data == NULL) { + return apr_psprintf(cmd->pool, "ModSecurity: Invalid regular expression: %s", p2); + } + dcfg->crypto_hash_framesrc_rx = 1; + } + + *(encryption_method **)apr_array_push(dcfg->encryption_method) = re; + + return NULL; +} + +/** +* \brief Add SecHttpBlKey configuration option +* +* \param cmd Pointer to configuration data +* \param _dcfg Pointer to directory configuration +* \param p1 Pointer to configuration option +* +* \retval NULL On success +*/ static const char *cmd_httpBl_key(cmd_parms *cmd, void *_dcfg, const char *p1) { directory_config *dcfg = (directory_config *)_dcfg; @@ -1980,7 +2519,7 @@ static const char *cmd_httpBl_key(cmd_parms *cmd, void *_dcfg, const char *p1) /* PCRE Limits */ static const char *cmd_pcre_match_limit(cmd_parms *cmd, - void *_dcfg, const char *p1) + void *_dcfg, const char *p1) { long val; @@ -1991,7 +2530,7 @@ static const char *cmd_pcre_match_limit(cmd_parms *cmd, val = atol(p1); if (val <= 0) { return apr_psprintf(cmd->pool, "ModSecurity: Invalid setting for " - "SecPcreMatchLimit: %s", p1); + "SecPcreMatchLimit: %s", p1); } msc_pcre_match_limit = (unsigned long int)val; @@ -1999,7 +2538,7 @@ static const char *cmd_pcre_match_limit(cmd_parms *cmd, } static const char *cmd_pcre_match_limit_recursion(cmd_parms *cmd, - void *_dcfg, const char *p1) + void *_dcfg, const char *p1) { long val; @@ -2010,7 +2549,7 @@ static const char *cmd_pcre_match_limit_recursion(cmd_parms *cmd, val = atol(p1); if (val <= 0) { return apr_psprintf(cmd->pool, "ModSecurity: Invalid setting for " - "SecPcreMatchLimitRecursion: %s", p1); + "SecPcreMatchLimitRecursion: %s", p1); } msc_pcre_match_limit_recursion = (unsigned long int)val; @@ -2021,7 +2560,7 @@ static const char *cmd_pcre_match_limit_recursion(cmd_parms *cmd, /* -- Geo Lookup configuration -- */ static const char *cmd_geo_lookup_db(cmd_parms *cmd, void *_dcfg, - const char *p1) + const char *p1) { const char *filename = resolve_relative_path(cmd->pool, cmd->directive->filename, p1); char *error_msg; @@ -2035,8 +2574,15 @@ static const char *cmd_geo_lookup_db(cmd_parms *cmd, void *_dcfg, return NULL; } -/* Unicode CodePage */ - +/** +* \brief Add SecUnicodeCodePage configuration option +* +* \param cmd Pointer to configuration data +* \param _dcfg Pointer to directory configuration +* \param p1 Pointer to configuration option +* +* \retval NULL On success +*/ static const char *cmd_unicode_codepage(cmd_parms *cmd, void *_dcfg, const char *p1) { @@ -2053,8 +2599,15 @@ static const char *cmd_unicode_codepage(cmd_parms *cmd, return NULL; } -/* Unicode Map */ - +/** +* \brief Add SecUnicodeMapFile configuration option +* +* \param cmd Pointer to configuration data +* \param _dcfg Pointer to directory configuration +* \param p1 Pointer to configuration option +* +* \retval NULL On success +*/ static const char *cmd_unicode_map(cmd_parms *cmd, void *_dcfg, const char *p1) { @@ -2070,8 +2623,15 @@ static const char *cmd_unicode_map(cmd_parms *cmd, void *_dcfg, return NULL; } -/* Google safe browsing */ - +/** +* \brief Add SecGsbLookupDb configuration option +* +* \param cmd Pointer to configuration data +* \param _dcfg Pointer to directory configuration +* \param p1 Pointer to configuration option +* +* \retval NULL On success +*/ static const char *cmd_gsb_lookup_db(cmd_parms *cmd, void *_dcfg, const char *p1) { @@ -2211,7 +2771,7 @@ const command_rec module_directives[] = { "SecArgumentSeparator", cmd_argument_separator, NULL, - CMD_SCOPE_MAIN, + CMD_SCOPE_ANY, "character that will be used as separator when parsing application/x-www-form-urlencoded content." ), @@ -2472,6 +3032,14 @@ const command_rec module_directives[] = { "On or Off" ), + AP_INIT_TAKE1 ( + "SecRulePerfTime", + cmd_rule_perf_time, + NULL, + CMD_SCOPE_ANY, + "Threshold to log slow rules in usecs." + ), + AP_INIT_TAKE1 ( "SecReadStateLimit", cmd_conn_read_state_limit, @@ -2624,6 +3192,22 @@ const command_rec module_directives[] = { "rule message for removal" ), + AP_INIT_TAKE2 ( + "SecEncryptionMethodPm", + cmd_encryption_method_pm, + NULL, + CMD_SCOPE_ANY, + "Encryption method and pattern" + ), + + AP_INIT_TAKE2 ( + "SecEncryptionMethodRx", + cmd_encryption_method_rx, + NULL, + CMD_SCOPE_ANY, + "Encryption method and regex" + ), + AP_INIT_TAKE2 ( "SecRuleUpdateActionById", cmd_rule_update_action_by_id, @@ -2640,6 +3224,23 @@ const command_rec module_directives[] = { "updated target list" ), + AP_INIT_TAKE23 ( + "SecRuleUpdateTargetByTag", + cmd_rule_update_target_by_tag, + NULL, + CMD_SCOPE_ANY, + "rule tag pattern and updated target list" + ), + + AP_INIT_TAKE23 ( + "SecRuleUpdateTargetByMsg", + cmd_rule_update_target_by_msg, + NULL, + CMD_SCOPE_ANY, + "rule message pattern and updated target list" + ), + + AP_INIT_TAKE1 ( "SecServerSignature", cmd_server_signature, @@ -2696,6 +3297,14 @@ const command_rec module_directives[] = { "id" ), + AP_INIT_TAKE1 ( + "SecSensorId", + cmd_sensor_id, + NULL, + CMD_SCOPE_MAIN, + "sensor id" + ), + AP_INIT_TAKE1 ( "SecHttpBlKey", cmd_httpBl_key, @@ -2704,5 +3313,29 @@ const command_rec module_directives[] = { "httpBl access key" ), + AP_INIT_TAKE1 ( + "SecEncryptionEngine", + cmd_encryption_engine, + NULL, + CMD_SCOPE_ANY, + "On or Off" + ), + + AP_INIT_TAKE2 ( + "SecEncryptionKey", + cmd_encryption_key, + NULL, + CMD_SCOPE_ANY, + "Set Encrytion key" + ), + + AP_INIT_TAKE1 ( + "SecEncryptionParam", + cmd_encryption_param, + NULL, + CMD_SCOPE_ANY, + "Set Encryption parameter" + ), + { NULL } }; diff --git a/apache2/apache2_io.c b/apache2/apache2_io.c index 00297d0c..88b34c3e 100644 --- a/apache2/apache2_io.c +++ b/apache2/apache2_io.c @@ -1,21 +1,22 @@ /* -* ModSecurity for Apache 2.x, http://www.modsecurity.org/ -* Copyright (c) 2004-2011 Trustwave Holdings, Inc. (http://www.trustwave.com/) -* -* You may not use this file except in compliance with -* the License.  You may obtain a copy of the License at -* -*     http://www.apache.org/licenses/LICENSE-2.0 -* -* If any of the files related to licensing are missing or if you have any -* other questions related to licensing please contact Trustwave Holdings, Inc. -* directly using the email address security@modsecurity.org. -*/ + * ModSecurity for Apache 2.x, http://www.modsecurity.org/ + * Copyright (c) 2004-2011 Trustwave Holdings, Inc. (http://www.trustwave.com/) + * + * You may not use this file except in compliance with + * the License.  You may obtain a copy of the License at + * + *     http://www.apache.org/licenses/LICENSE-2.0 + * + * If any of the files related to licensing are missing or if you have any + * other questions related to licensing please contact Trustwave Holdings, Inc. + * directly using the email address security@modsecurity.org. + */ #include #include "modsecurity.h" #include "apache2.h" +#include "msc_crypt.h" /* -- Input filter -- */ @@ -29,7 +30,7 @@ static void dummy_free_func(void *data) {} * processing module). */ apr_status_t input_filter(ap_filter_t *f, apr_bucket_brigade *bb_out, - ap_input_mode_t mode, apr_read_type_e block, apr_off_t nbytes) + ap_input_mode_t mode, apr_read_type_e block, apr_off_t nbytes) { modsec_rec *msr = (modsec_rec *)f->ctx; msc_data_chunk *chunk = NULL; @@ -39,7 +40,7 @@ apr_status_t input_filter(ap_filter_t *f, apr_bucket_brigade *bb_out, if (msr == NULL) { ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, f->r->server, - "ModSecurity: Internal error in input filter: msr is null."); + "ModSecurity: Internal error in input filter: msr is null."); ap_remove_input_filter(f); return APR_EGENERAL; } @@ -48,8 +49,8 @@ apr_status_t input_filter(ap_filter_t *f, apr_bucket_brigade *bb_out, msr->r = f->r; if (msr->phase < PHASE_REQUEST_BODY) { - msr_log(msr, 1, "Internal error: REQUEST_BODY phase incomplete for input filter in phase %d", msr->phase); - return APR_EGENERAL; + msr_log(msr, 1, "Internal error: REQUEST_BODY phase incomplete for input filter in phase %d", msr->phase); + return APR_EGENERAL; } if ((msr->if_status == IF_STATUS_COMPLETE)||(msr->if_status == IF_STATUS_NONE)) { @@ -62,7 +63,7 @@ apr_status_t input_filter(ap_filter_t *f, apr_bucket_brigade *bb_out, if (msr->txcfg->debuglog_level >= 4) { msr_log(msr, 4, "Input filter: Forwarding input: mode=%d, block=%d, nbytes=%" APR_OFF_T_FMT - " (f %pp, r %pp).", mode, block, nbytes, f, f->r); + " (f %pp, r %pp).", mode, block, nbytes, f, f->r); } if (msr->if_started_forwarding == 0) { @@ -87,24 +88,24 @@ apr_status_t input_filter(ap_filter_t *f, apr_bucket_brigade *bb_out, if (chunk && (!msr->txcfg->stream_inbody_inspection || (msr->txcfg->stream_inbody_inspection && msr->if_stream_changed == 0))) { /* Copy the data we received in the chunk */ bucket = apr_bucket_heap_create(chunk->data, chunk->length, NULL, - f->r->connection->bucket_alloc); + f->r->connection->bucket_alloc); - #if 0 +#if 0 It would seem that we cannot prevent other filters in the chain - from modifying data in-place. Hence we copy. + from modifying data in-place. Hence we copy. - if (chunk->is_permanent) { - /* Do not make a copy of the data we received in the chunk. */ - bucket = apr_bucket_heap_create(chunk->data, chunk->length, dummy_free_func, - f->r->connection->bucket_alloc); - } else { - /* Copy the data we received in the chunk. */ - bucket = apr_bucket_heap_create(chunk->data, chunk->length, NULL, - f->r->connection->bucket_alloc); - } + if (chunk->is_permanent) { + /* Do not make a copy of the data we received in the chunk. */ + bucket = apr_bucket_heap_create(chunk->data, chunk->length, dummy_free_func, + f->r->connection->bucket_alloc); + } else { + /* Copy the data we received in the chunk. */ + bucket = apr_bucket_heap_create(chunk->data, chunk->length, NULL, + f->r->connection->bucket_alloc); + } - #endif +#endif if (bucket == NULL) return APR_EGENERAL; APR_BRIGADE_INSERT_TAIL(bb_out, bucket); @@ -226,8 +227,8 @@ apr_status_t read_request_body(modsec_rec *msr, char **error_msg) { * to extract the size of the data available. */ for(bucket = APR_BRIGADE_FIRST(bb_in); - bucket != APR_BRIGADE_SENTINEL(bb_in); - bucket = APR_BUCKET_NEXT(bucket)) + bucket != APR_BRIGADE_SENTINEL(bb_in); + bucket = APR_BUCKET_NEXT(bucket)) { const char *buf; apr_size_t buflen; @@ -240,7 +241,7 @@ apr_status_t read_request_body(modsec_rec *msr, char **error_msg) { if (msr->txcfg->debuglog_level >= 9) { msr_log(msr, 9, "Input filter: Bucket type %s contains %" APR_SIZE_T_FMT " bytes.", - bucket->type->name, buflen); + bucket->type->name, buflen); } /* Check request body limit (should only trigger on chunked requests). */ @@ -393,7 +394,7 @@ static int output_filter_should_run(modsec_rec *msr, request_rec *r) { * Initialises the output filter. */ static apr_status_t output_filter_init(modsec_rec *msr, ap_filter_t *f, - apr_bucket_brigade *bb_in) + apr_bucket_brigade *bb_in) { request_rec *r = f->r; const char *s_content_length = NULL; @@ -434,7 +435,7 @@ static apr_status_t output_filter_init(modsec_rec *msr, ap_filter_t *f, len = strtol(s_content_length, NULL, 10); if ((len == LONG_MIN)||(len == LONG_MAX)||(len < 0)||(len >= 1073741824)) { msr_log(msr, 1, "Output filter: Invalid Content-Length: %s", log_escape_nq(r->pool, - (char *)s_content_length)); + (char *)s_content_length)); return -1; /* Invalid. */ } @@ -448,7 +449,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); + log_escape_nq(r->pool, (char *)s_content_length), msr->txcfg->of_limit); msr->outbound_error = 1; return -2; /* Over the limit. */ } @@ -480,12 +481,12 @@ static apr_status_t send_of_brigade(modsec_rec *msr, ap_filter_t *f) { /* Look like this is caused by the error * already being handled, so we should ignore it * - msr_log(msr, log_level, "Output filter: Error while forwarding response data (%d): Filter error", rc); + msr_log(msr, log_level, "Output filter: Error while forwarding response data (%d): Filter error", rc); */ break; default : msr_log(msr, log_level, "Output filter: Error while forwarding response data (%d): %s", - rc, get_apr_error(msr->mp, rc)); + rc, get_apr_error(msr->mp, rc)); break; } } @@ -496,6 +497,12 @@ static apr_status_t send_of_brigade(modsec_rec *msr, ap_filter_t *f) { return APR_SUCCESS; } +/** \brief Inject data into brigade + * + * \param msr ModSecurity transation resource + * \param ap_filter_t Apache filter + * + */ static void inject_content_to_of_brigade(modsec_rec *msr, ap_filter_t *f) { apr_bucket *b; @@ -567,7 +574,7 @@ static int flatten_response_body(modsec_rec *msr) { msr->resbody_data[msr->resbody_length] = '\0'; msr->resbody_status = RESBODY_STATUS_READ; - if (msr->txcfg->stream_outbody_inspection) { + if (msr->txcfg->stream_outbody_inspection && msr->txcfg->encryption_is_enabled == ENCRYPTION_DISABLED) { msr->stream_output_length = msr->resbody_length; @@ -580,6 +587,36 @@ static int flatten_response_body(modsec_rec *msr) { memset(msr->stream_output_data, 0, msr->stream_output_length+1); strncpy(msr->stream_output_data, msr->resbody_data, msr->stream_output_length); msr->stream_output_data[msr->stream_output_length] = '\0'; + } else if (msr->txcfg->stream_outbody_inspection && msr->txcfg->encryption_is_enabled == ENCRYPTION_ENABLED) { + int retval = 0; + apr_time_t time1 = apr_time_now(); + + retval = init_response_body_html_parser(msr); + + if(retval == 1) { + retval = encrypt_response_body_links(msr); + if(retval > 0) { + retval = inject_encrypted_response_body(msr, retval); + if (msr->txcfg->debuglog_level >= 4) { + msr_log(msr, 4, "Encryption completed in %" APR_TIME_T_FMT " usec.", (apr_time_now() - time1)); + } + + } + } + + if(msr->of_stream_changed == 0) { + msr->stream_output_length = msr->resbody_length; + + if (msr->stream_output_data == NULL) { + msr_log(msr, 1, "Output filter: Stream Response body data memory allocation failed. Asked for: %" APR_SIZE_T_FMT, + msr->stream_output_length + 1); + return -1; + } + + memset(msr->stream_output_data, 0, msr->stream_output_length+1); + strncpy(msr->stream_output_data, msr->resbody_data, msr->stream_output_length); + msr->stream_output_data[msr->stream_output_length] = '\0'; + } } return 1; @@ -598,7 +635,7 @@ apr_status_t output_filter(ap_filter_t *f, apr_bucket_brigade *bb_in) { /* Do we have the context? */ if (msr == NULL) { ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, f->r->server, - "ModSecurity: Internal Error: msr is null in output filter."); + "ModSecurity: Internal Error: msr is null in output filter."); ap_remove_output_filter(f); return send_error_bucket(msr, f, HTTP_INTERNAL_SERVER_ERROR); } @@ -631,8 +668,12 @@ apr_status_t output_filter(ap_filter_t *f, apr_bucket_brigade *bb_in) { msr->r = r; msr->response_status = r->status; msr->status_line = ((r->status_line != NULL) - ? r->status_line : ap_get_status_line(r->status)); + ? r->status_line : ap_get_status_line(r->status)); msr->response_protocol = get_response_protocol(r); + + if(msr->txcfg->crypto_hash_location_rx == 1 || msr->txcfg->crypto_hash_location_pm == 1) + rc = modify_response_header(msr); + msr->response_headers = apr_table_overlay(msr->mp, r->err_headers_out, r->headers_out); /* Process phase RESPONSE_HEADERS */ @@ -702,28 +743,28 @@ apr_status_t output_filter(ap_filter_t *f, apr_bucket_brigade *bb_in) { /* Content injection (prepend & non-buffering). */ if ((msr->txcfg->content_injection_enabled) && (msr->content_prepend) && (msr->of_skipping)) { apr_bucket *bucket_ci = apr_bucket_heap_create(msr->content_prepend, - msr->content_prepend_len, NULL, f->r->connection->bucket_alloc); + msr->content_prepend_len, NULL, f->r->connection->bucket_alloc); APR_BRIGADE_INSERT_HEAD(bb_in, bucket_ci); if (msr->txcfg->debuglog_level >= 9) { msr_log(msr, 9, "Content Injection (nb): Added content to top: %s", - log_escape_nq_ex(msr->mp, msr->content_prepend, msr->content_prepend_len)); + log_escape_nq_ex(msr->mp, msr->content_prepend, msr->content_prepend_len)); } } } else - if (msr->of_status == OF_STATUS_COMPLETE) { - msr_log(msr, 1, "Output filter: Internal error: output filtering complete yet filter was invoked."); - ap_remove_output_filter(f); - return APR_EGENERAL; - } + if (msr->of_status == OF_STATUS_COMPLETE) { + msr_log(msr, 1, "Output filter: Internal error: output filtering complete yet filter was invoked."); + ap_remove_output_filter(f); + return APR_EGENERAL; + } /* Loop through the buckets in the brigade in order * to extract the size of the data available. */ for(bucket = APR_BRIGADE_FIRST(bb_in); - bucket != APR_BRIGADE_SENTINEL(bb_in); - bucket = APR_BUCKET_NEXT(bucket)) { + bucket != APR_BRIGADE_SENTINEL(bb_in); + bucket = APR_BUCKET_NEXT(bucket)) { const char *buf; apr_size_t buflen; @@ -738,7 +779,7 @@ apr_status_t output_filter(ap_filter_t *f, apr_bucket_brigade *bb_in) { msr->resbody_status = RESBODY_STATUS_ERROR; msr_log(msr, 1, "Output filter: Failed to read bucket (rc %d): %s", - rc, get_apr_error(r->pool, rc)); + rc, get_apr_error(r->pool, rc)); ap_remove_output_filter(f); return send_error_bucket(msr, f, HTTP_INTERNAL_SERVER_ERROR); @@ -746,7 +787,7 @@ apr_status_t output_filter(ap_filter_t *f, apr_bucket_brigade *bb_in) { if (msr->txcfg->debuglog_level >= 9) { msr_log(msr, 9, "Output filter: Bucket type %s contains %" APR_SIZE_T_FMT " bytes.", - bucket->type->name, buflen); + bucket->type->name, buflen); } /* Check the response size. */ @@ -759,7 +800,7 @@ apr_status_t output_filter(ap_filter_t *f, apr_bucket_brigade *bb_in) { 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, " - "total not specified).", msr->txcfg->of_limit); + "total not specified).", msr->txcfg->of_limit); msr->of_status = OF_STATUS_COMPLETE; msr->resbody_status = RESBODY_STATUS_PARTIAL; @@ -773,7 +814,7 @@ apr_status_t output_filter(ap_filter_t *f, apr_bucket_brigade *bb_in) { if (msr->txcfg->debuglog_level >= 4) { msr_log(msr, 4, "Output filter: Processing partial response body (limit %ld)", - msr->txcfg->of_limit); + msr->txcfg->of_limit); } } } else { @@ -787,17 +828,17 @@ apr_status_t output_filter(ap_filter_t *f, apr_bucket_brigade *bb_in) { /* Inject content (append & non-buffering). */ if ((msr->txcfg->content_injection_enabled) && (msr->content_append) - && (msr->of_skipping || msr->of_partial || start_skipping)) + && (msr->of_skipping || msr->of_partial || start_skipping)) { apr_bucket *bucket_ci = NULL; bucket_ci = apr_bucket_heap_create(msr->content_append, - msr->content_append_len, NULL, f->r->connection->bucket_alloc); + msr->content_append_len, NULL, f->r->connection->bucket_alloc); APR_BUCKET_INSERT_BEFORE(bucket, bucket_ci); if (msr->txcfg->debuglog_level >= 9) { msr_log(msr, 9, "Content-Injection (nb): Added content to bottom: %s", - log_escape_nq_ex(msr->mp, msr->content_append, msr->content_append_len)); + log_escape_nq_ex(msr->mp, msr->content_append, msr->content_append_len)); } } @@ -867,7 +908,7 @@ apr_status_t output_filter(ap_filter_t *f, apr_bucket_brigade *bb_in) { if (msr->txcfg->debuglog_level >= 4) { msr_log(msr, 4, "Output filter: Completed receiving response body (buffered %s - %" APR_SIZE_T_FMT " bytes).", - (msr->of_partial ? "partial" : "full"), msr->resbody_length); + (msr->of_partial ? "partial" : "full"), msr->resbody_length); } } else { /* Not looking at response data. */ if (msr->of_done_reading == 0) { @@ -950,12 +991,12 @@ apr_status_t output_filter(ap_filter_t *f, apr_bucket_brigade *bb_in) { apr_bucket *bucket_ci = NULL; bucket_ci = apr_bucket_heap_create(msr->content_append, - msr->content_append_len, NULL, f->r->connection->bucket_alloc); + msr->content_append_len, NULL, f->r->connection->bucket_alloc); APR_BUCKET_INSERT_BEFORE(eos_bucket, bucket_ci); if (msr->txcfg->debuglog_level >= 9) { msr_log(msr, 9, "Content-Injection (b): Added content to bottom: %s", - log_escape_nq_ex(msr->mp, msr->content_append, msr->content_append_len)); + log_escape_nq_ex(msr->mp, msr->content_append, msr->content_append_len)); } } diff --git a/apache2/mod_security2.c b/apache2/mod_security2.c index 3265e8bf..e3177079 100644 --- a/apache2/mod_security2.c +++ b/apache2/mod_security2.c @@ -33,6 +33,11 @@ #include "apr_version.h" +#if defined(WITH_LUA) +#include "msc_lua.h" +#endif + + /* ModSecurity structure */ msc_engine DSOLOCAL *modsecurity = NULL; @@ -68,7 +73,7 @@ typedef struct { /* -- Miscellaneous functions -- */ -/* +/** * \brief Print informations from used libraries * * \param mp Pointer to memory pool @@ -114,6 +119,7 @@ int perform_interception(modsec_rec *msr) { msre_actionset *actionset = NULL; const char *message = NULL; const char *phase_text = ""; + unsigned int pause = 0; int status = DECLINED; int log_level = 1; @@ -142,11 +148,31 @@ int perform_interception(modsec_rec *msr) { log_level = (actionset->log != 1) ? 4 : 1; /* Pause the request first (if configured and the initial request). */ - if (actionset->intercept_pause) { - msr_log(msr, (log_level > 3 ? log_level : log_level + 1), "Pausing transaction for " - "%d msec.", actionset->intercept_pause); - /* apr_sleep accepts microseconds */ - apr_sleep((apr_interval_time_t)(actionset->intercept_pause * 1000)); + if (actionset->intercept_pause != NULL) { + if(strstr(actionset->intercept_pause,"%{") != NULL) { + msc_string *var = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); + + var->value = (char *)actionset->intercept_pause; + var->value_len = strlen(actionset->intercept_pause); + expand_macros(msr, var, NULL, msr->mp); + + pause = atoi(var->value); + if ((pause == LONG_MAX)||(pause == LONG_MIN)||(pause <= 0)) + pause = 0; + + msr_log(msr, (log_level > 3 ? log_level : log_level + 1), "Pausing transaction for " + "%d msec.", pause); + /* apr_sleep accepts microseconds */ + apr_sleep((apr_interval_time_t)(pause * 1000)); + } else { + pause = atoi(actionset->intercept_pause); + if ((pause == LONG_MAX)||(pause == LONG_MIN)||(pause <= 0)) + pause = 0; + msr_log(msr, (log_level > 3 ? log_level : log_level + 1), "Pausing transaction for " + "%d msec.", pause); + /* apr_sleep accepts microseconds */ + apr_sleep((apr_interval_time_t)(pause * 1000)); + } } /* Determine how to respond and prepare the log message. */ @@ -155,13 +181,13 @@ int perform_interception(modsec_rec *msr) { if (actionset->intercept_status != 0) { status = actionset->intercept_status; message = apr_psprintf(msr->mp, "Access denied with code %d%s.", - status, phase_text); + status, phase_text); } else { log_level = 1; status = HTTP_INTERNAL_SERVER_ERROR; message = apr_psprintf(msr->mp, "Access denied with code 500%s " - "(Internal Error: Invalid status code requested %d).", - phase_text, actionset->intercept_status); + "(Internal Error: Invalid status code requested %d).", + phase_text, actionset->intercept_status); } break; @@ -233,18 +259,39 @@ int perform_interception(modsec_rec *msr) { break; case ACTION_REDIRECT : - apr_table_setn(msr->r->headers_out, "Location", actionset->intercept_uri); - if ((actionset->intercept_status == 301)||(actionset->intercept_status == 302) - ||(actionset->intercept_status == 303)||(actionset->intercept_status == 307)) - { - status = actionset->intercept_status; + if(strstr(actionset->intercept_uri,"%{") != NULL) { + msc_string *var = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); + + var->value = (char *)actionset->intercept_uri; + var->value_len = strlen(actionset->intercept_uri); + expand_macros(msr, var, NULL, msr->mp); + + apr_table_setn(msr->r->headers_out, "Location", var->value); + if ((actionset->intercept_status == 301)||(actionset->intercept_status == 302) + ||(actionset->intercept_status == 303)||(actionset->intercept_status == 307)) + { + status = actionset->intercept_status; + } else { + status = HTTP_MOVED_TEMPORARILY; + } + message = apr_psprintf(msr->mp, "Access denied with redirection to %s using " + "status %d%s.", + log_escape_nq(msr->mp, var->value), status, + phase_text); } else { - status = HTTP_MOVED_TEMPORARILY; + apr_table_setn(msr->r->headers_out, "Location", actionset->intercept_uri); + if ((actionset->intercept_status == 301)||(actionset->intercept_status == 302) + ||(actionset->intercept_status == 303)||(actionset->intercept_status == 307)) + { + status = actionset->intercept_status; + } else { + status = HTTP_MOVED_TEMPORARILY; + } + message = apr_psprintf(msr->mp, "Access denied with redirection to %s using " + "status %d%s.", + log_escape_nq(msr->mp, actionset->intercept_uri), status, + phase_text); } - message = apr_psprintf(msr->mp, "Access denied with redirection to %s using " - "status %d%s.", - log_escape_nq(msr->mp, actionset->intercept_uri), status, - phase_text); break; case ACTION_ALLOW : @@ -272,8 +319,8 @@ int perform_interception(modsec_rec *msr) { log_level = 1; status = HTTP_INTERNAL_SERVER_ERROR; message = apr_psprintf(msr->mp, "Access denied with code 500%s " - "(Internal Error: invalid interception action %d).", - phase_text, actionset->intercept_action); + "(Internal Error: invalid interception action %d).", + phase_text, actionset->intercept_action); break; } @@ -365,7 +412,15 @@ static modsec_rec *create_tx_context(request_rec *r) { msr->r_early = r; msr->request_time = r->request_time; msr->dcfg1 = (directory_config *)ap_get_module_config(r->per_dir_config, - &security2_module); + &security2_module); + +#if defined(WITH_LUA) + #ifdef CACHE_LUA + msr->L = lua_open(); + luaL_openlibs(msr->L); + #endif +#endif + /** * Create a special user configuration. This is where @@ -393,7 +448,7 @@ static modsec_rec *create_tx_context(request_rec *r) { msr->txid = get_env_var(r, "UNIQUE_ID"); if (msr->txid == NULL) { ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r->server, - "ModSecurity: ModSecurity requires mod_unique_id to be installed."); + "ModSecurity: ModSecurity requires mod_unique_id to be installed."); return NULL; } @@ -409,12 +464,13 @@ static modsec_rec *create_tx_context(request_rec *r) { msr->local_addr = r->connection->local_ip; msr->local_port = r->connection->local_addr->port; -#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER > 2 - msr->remote_addr = r->connection->client_ip; - msr->remote_port = r->connection->client_addr->port; -#else +#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER < 3 msr->remote_addr = r->connection->remote_ip; msr->remote_port = r->connection->remote_addr->port; +#else + msr->remote_addr = r->connection->client_ip; + msr->remote_port = r->connection->client_addr->port; + msr->useragent_ip = r->useragent_ip; #endif msr->request_line = r->the_request; @@ -459,7 +515,7 @@ static apr_status_t change_server_signature(server_rec *s) { if (server_version == NULL) { ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, s, - "SecServerSignature: Apache returned null as signature."); + "SecServerSignature: Apache returned null as signature."); return -1; } @@ -468,8 +524,8 @@ static apr_status_t change_server_signature(server_rec *s) { } else { ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, s, - "SecServerSignature: original signature too short. Please set " - "ServerTokens to Full."); + "SecServerSignature: original signature too short. Please set " + "ServerTokens to Full."); return -1; } @@ -520,7 +576,7 @@ static int hook_pre_config(apr_pool_t *mp, apr_pool_t *mp_log, apr_pool_t *mp_te modsecurity = modsecurity_create(mp, MODSEC_ONLINE); if (modsecurity == NULL) { ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, - "ModSecurity: Failed to initialise engine."); + "ModSecurity: Failed to initialise engine."); return HTTP_INTERNAL_SERVER_ERROR; } @@ -548,7 +604,7 @@ static int hook_post_config(apr_pool_t *mp, apr_pool_t *mp_log, apr_pool_t *mp_t if (init_flag == NULL) { first_time = 1; apr_pool_userdata_set((const void *)1, "modsecurity-init-flag", - apr_pool_cleanup_null, s->process->pool); + apr_pool_cleanup_null, s->process->pool); } else { modsecurity_init(modsecurity, mp); } @@ -562,7 +618,7 @@ static int hook_post_config(apr_pool_t *mp, apr_pool_t *mp_log, apr_pool_t *mp_t change_server_signature(s); } - #if (!(defined(WIN32) || defined(NETWARE))) +#if (!(defined(WIN32) || defined(NETWARE))) /* Internal chroot functionality */ @@ -574,37 +630,37 @@ static int hook_post_config(apr_pool_t *mp, apr_pool_t *mp_log, apr_pool_t *mp_t if (first_time == 0) { ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0, s, - "ModSecurity: chroot checkpoint #2 (pid=%ld ppid=%ld)", (long)getpid(), (long)getppid()); + "ModSecurity: chroot checkpoint #2 (pid=%ld ppid=%ld)", (long)getpid(), (long)getppid()); if (chdir(chroot_dir) < 0) { ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, s, - "ModSecurity: chroot failed, unable to chdir to %s, errno=%d (%s)", - chroot_dir, errno, strerror(errno)); + "ModSecurity: chroot failed, unable to chdir to %s, errno=%d (%s)", + chroot_dir, errno, strerror(errno)); exit(1); } if (chroot(chroot_dir) < 0) { ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, s, - "ModSecurity: chroot failed, path=%s, errno=%d(%s)", - chroot_dir, errno, strerror(errno)); + "ModSecurity: chroot failed, path=%s, errno=%d(%s)", + chroot_dir, errno, strerror(errno)); exit(1); } if (chdir("/") < 0) { ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, s, - "ModSecurity: chdoot failed, unable to chdir to /, errno=%d (%s)", - errno, strerror(errno)); + "ModSecurity: chdoot failed, unable to chdir to /, errno=%d (%s)", + errno, strerror(errno)); exit(1); } ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0, s, - "ModSecurity: chroot successful, path=%s", chroot_dir); + "ModSecurity: chroot successful, path=%s", chroot_dir); } else { ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0, s, - "ModSecurity: chroot checkpoint #1 (pid=%ld ppid=%ld)", (long)getpid(), (long)getppid()); + "ModSecurity: chroot checkpoint #1 (pid=%ld ppid=%ld)", (long)getpid(), (long)getppid()); } } - #endif +#endif /* Schedule main cleanup for later, when the main pool is destroyed. */ apr_pool_cleanup_register(mp, (void *)s, module_cleanup, apr_pool_cleanup_null); @@ -619,7 +675,7 @@ static int hook_post_config(apr_pool_t *mp, apr_pool_t *mp_log, apr_pool_t *mp_t /* If we've changed the server signature make note of the original. */ if (new_server_signature != NULL) { ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0, s, - "Original server signature: %s", real_server_signature); + "Original server signature: %s", real_server_signature); } } @@ -642,6 +698,7 @@ static void hook_child_init(apr_pool_t *mp, server_rec *s) { */ static int hook_request_early(request_rec *r) { modsec_rec *msr = NULL; + int rc = DECLINED; /* This function needs to run only once per transaction * (i.e. subrequests and redirects are excluded). @@ -656,22 +713,35 @@ static int hook_request_early(request_rec *r) { msr = create_tx_context(r); if (msr == NULL) return DECLINED; - #if 0 - /* NOTE This check is not currently needed, but it may be needed in the - * future when we add another early phase. - */ +#ifdef REQUEST_EARLY /* Are we allowed to continue? */ if (msr->txcfg->is_enabled == MODSEC_DISABLED) { if (msr->txcfg->debuglog_level >= 4) { msr_log(msr, 4, "Processing disabled, skipping (hook request_early)."); } - return DECLINED; - } - #endif + } - return DECLINED; + /* Process phase REQUEST_HEADERS */ + if (modsecurity_process_phase(msr, PHASE_REQUEST_HEADERS) > 0) { + rc = perform_interception(msr); + } + + if ( (msr->txcfg->is_enabled != MODSEC_DISABLED) + && (msr->txcfg->reqbody_access == 1) + && (rc == DECLINED)) + { + /* 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); + if(msr->txcfg->is_enabled != MODSEC_DETECTION_ONLY) + return HTTP_REQUEST_ENTITY_TOO_LARGE; + } + } +#endif + return rc; } /** @@ -712,7 +782,7 @@ static int hook_request_late(request_rec *r) { /* Get the second configuration context. */ msr->dcfg2 = (directory_config *)ap_get_module_config(r->per_dir_config, - &security2_module); + &security2_module); /* Create a transaction context. */ msr->txcfg = create_directory_config(msr->mp, NULL); @@ -731,10 +801,10 @@ static int hook_request_late(request_rec *r) { if (msr->txcfg->debuglog_level >= 4) { msr_log(msr, 4, "Processing disabled, skipping (hook request_late)."); } - return DECLINED; } +#ifndef REQUEST_EARLY /* Phase 1 */ if (msr->txcfg->debuglog_level >= 4) { msr_log(msr, 4, "First phase starting (dcfg %pp).", msr->dcfg2); @@ -749,6 +819,7 @@ static int hook_request_late(request_rec *r) { return rc; } } +#endif /* The rule engine could have been disabled in phase 1. */ if (msr->txcfg->is_enabled == MODSEC_DISABLED) { @@ -933,8 +1004,8 @@ static void hook_error_log(const char *file, int line, int level, apr_status_t s } } } - if (msr == NULL) return; + /* Store the error message for later */ em = (error_message *)apr_pcalloc(msr->mp, sizeof(error_message)); if (em == NULL) return; @@ -952,7 +1023,6 @@ static void hook_error_log(const char *file, int line, int level, apr_status_t s em->status = status; if (fmt != NULL) em->message = apr_pstrdup(msr->mp, fmt); #endif - /* Remove \n from the end of the message */ if (em->message != NULL) { char *p = (char *)em->message; @@ -968,6 +1038,7 @@ static void hook_error_log(const char *file, int line, int level, apr_status_t s *(const error_message **)apr_array_push(msr->error_messages) = em; } + /** * Guardian logger is used to interface to the external * script for web server protection - httpd_guardian. @@ -1007,7 +1078,7 @@ static void sec_guardian_logger(request_rec *r, request_rec *origr, modsec_rec * */ str2 = apr_psprintf(msr->mp, "%" APR_TIME_T_FMT " %" APR_TIME_T_FMT " \"%s\" %d", - duration, apr_time_sec(duration), log_escape(msr->mp, modsec_message), modsec_rating); + duration, apr_time_sec(duration), log_escape(msr->mp, modsec_message), modsec_rating); if (str2 == NULL) return; /* We do not want the index line to be longer than 3980 bytes. */ @@ -1087,7 +1158,7 @@ static int hook_log_transaction(request_rec *r) { msr->r = r; msr->response_status = r->status; msr->status_line = ((r->status_line != NULL) - ? r->status_line : ap_get_status_line(r->status)); + ? r->status_line : ap_get_status_line(r->status)); msr->response_protocol = get_response_protocol(origr); msr->response_headers = apr_table_copy(msr->mp, r->headers_out); if (!r->assbackwards) msr->response_headers_sent = 1; @@ -1121,7 +1192,7 @@ static void hook_insert_filter(request_rec *r) { if (msr->if_status == IF_STATUS_WANTS_TO_RUN) { if (msr->txcfg->debuglog_level >= 4) { msr_log(msr, 4, "Hook insert_filter: Adding input forwarding filter %s(r %pp).", - (((r->main != NULL)||(r->prev != NULL)) ? "for subrequest " : ""), r); + (((r->main != NULL)||(r->prev != NULL)) ? "for subrequest " : ""), r); } ap_add_input_filter("MODSECURITY_IN", msr, r, r->connection); @@ -1220,15 +1291,15 @@ 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 -*/ +/** + * \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; diff --git a/apache2/modsecurity.c b/apache2/modsecurity.c index 11615c29..6e37d57b 100644 --- a/apache2/modsecurity.c +++ b/apache2/modsecurity.c @@ -260,6 +260,12 @@ static apr_status_t modsecurity_tx_cleanup(void *data) { msr_log(msr, 1, "%s", my_error_msg); } +#if defined(WITH_LUA) + #ifdef CACHE_LUA + if(msr->L != NULL) lua_close(msr->L); + #endif +#endif + return APR_SUCCESS; } @@ -378,6 +384,12 @@ apr_status_t modsecurity_tx_init(modsec_rec *msr) { if (msr->matched_vars == NULL) return -1; apr_table_clear(msr->matched_vars); + if(msr->txcfg->max_rule_time > 0) { + msr->perf_rules = apr_table_make(msr->mp, 8); + if (msr->perf_rules == NULL) return -1; + apr_table_clear(msr->perf_rules); + } + /* Locate the cookie headers and parse them */ arr = apr_table_elts(msr->request_headers); te = (apr_table_entry_t *)arr->elts; @@ -423,6 +435,9 @@ apr_status_t modsecurity_tx_init(modsec_rec *msr) { msr->removed_rules_tag = apr_array_make(msr->mp, 16, sizeof(char *)); if (msr->removed_rules_tag == NULL) return -1; + msr->removed_rules_msg = apr_array_make(msr->mp, 16, sizeof(char *)); + if (msr->removed_rules_msg == NULL) return -1; + return 1; } diff --git a/apache2/modsecurity.h b/apache2/modsecurity.h index 86508d6e..30661a8f 100644 --- a/apache2/modsecurity.h +++ b/apache2/modsecurity.h @@ -19,8 +19,11 @@ #include #include +#include +#include typedef struct rule_exception rule_exception; +typedef struct rule_exception encryption_method; typedef struct modsec_rec modsec_rec; typedef struct directory_config directory_config; typedef struct error_message error_message; @@ -40,6 +43,7 @@ typedef struct msc_parm msc_parm; #include "msc_gsb.h" #include "msc_unicode.h" #include "re.h" +#include "msc_crypt.h" #include "ap_config.h" #include "apr_md5.h" @@ -50,6 +54,11 @@ typedef struct msc_parm msc_parm; #include "http_log.h" #include "http_protocol.h" +#if defined(WITH_LUA) +#include "msc_lua.h" +#endif + + #define PHASE_REQUEST_HEADERS 1 #define PHASE_REQUEST_BODY 2 #define PHASE_RESPONSE_HEADERS 3 @@ -101,7 +110,7 @@ typedef struct msc_parm msc_parm; #define SECMARKER_TARGETS "REMOTE_ADDR" #define SECMARKER_ARGS "@noMatch" -#define SECMARKER_BASE_ACTIONS "t:none,pass,id:" +#define SECMARKER_BASE_ACTIONS "t:none,pass,marker:" #if !defined(OS2) && !defined(WIN32) && !defined(BEOS) && !defined(NETWARE) #include "unixd.h" @@ -172,6 +181,24 @@ extern DSOLOCAL int *unicode_map_table; #define MODSEC_DETECTION_ONLY 1 #define MODSEC_ENABLED 2 +#define ENCRYPTION_DISABLED 0 +#define ENCRYPTION_ENABLED 1 + +#define ENCRYPTION_URL_HREF_HASH_RX 0 +#define ENCRYPTION_URL_HREF_HASH_PM 1 +#define ENCRYPTION_URL_FACTION_HASH_RX 2 +#define ENCRYPTION_URL_FACTION_HASH_PM 3 +#define ENCRYPTION_URL_LOCATION_HASH_RX 4 +#define ENCRYPTION_URL_LOCATION_HASH_PM 5 +#define ENCRYPTION_URL_IFRAMESRC_HASH_RX 6 +#define ENCRYPTION_URL_IFRAMESRC_HASH_PM 7 +#define ENCRYPTION_URL_FRAMESRC_HASH_RX 8 +#define ENCRYPTION_URL_FRAMESRC_HASH_PM 9 + +#define ENCRYPTION_KEYONLY 0 +#define ENCRYPTION_SESSIONID 1 +#define ENCRYPTION_REMOTEIP 2 + #define MODSEC_CACHE_DISABLED 0 #define MODSEC_CACHE_ENABLED 1 @@ -259,6 +286,9 @@ struct modsec_rec { unsigned int remote_port; const char *remote_user; + /* useragent */ + const char *useragent_ip; + /* request */ const char *request_line; @@ -342,7 +372,7 @@ struct modsec_rec { const char *intercept_message; /* performance measurement */ - apr_time_t request_time; + apr_time_t request_time; apr_time_t time_phase1; apr_time_t time_phase2; apr_time_t time_phase3; @@ -352,7 +382,8 @@ struct modsec_rec { apr_time_t time_storage_write; apr_time_t time_logging; apr_time_t time_gc; - + apr_table_t *perf_rules; + apr_array_header_t *matched_rules; msc_string *matched_var; int highest_severity; @@ -383,6 +414,7 @@ struct modsec_rec { /* removed rules */ apr_array_header_t *removed_rules; apr_array_header_t *removed_rules_tag; + apr_array_header_t *removed_rules_msg; /* When "allow" is executed the variable below is * updated to contain the scope of the allow action. Set @@ -397,6 +429,13 @@ struct modsec_rec { /* Generic request body processor context to be used by custom parsers. */ void *reqbody_processor_ctx; + + htmlDocPtr crypto_html_tree; +#if defined(WITH_LUA) + #ifdef CACHE_LUA + lua_State *L; + #endif +#endif }; struct directory_config { @@ -432,6 +471,9 @@ struct directory_config { /* -- Audit log -- */ + /* Max rule time */ + int max_rule_time; + /* Whether audit log should be enabled in the context or not */ int auditlog_flag; @@ -486,6 +528,7 @@ struct directory_config { /* Misc */ const char *data_dir; const char *webappid; + const char *sensor_id; const char *httpBlkey; /* Content injection. */ @@ -523,6 +566,25 @@ struct directory_config { /* Collection timeout */ int col_timeout; + + /* Encryption */ + apr_array_header_t *encryption_method; + const char *crypto_key; + int crypto_key_len; + const char *crypto_param_name; + int encryption_is_enabled; + int encryption_enforcement; + int crypto_key_add; + int crypto_hash_href_rx; + int crypto_hash_faction_rx; + int crypto_hash_location_rx; + int crypto_hash_iframesrc_rx; + int crypto_hash_framesrc_rx; + int crypto_hash_href_pm; + int crypto_hash_faction_pm; + int crypto_hash_location_pm; + int crypto_hash_iframesrc_pm; + int crypto_hash_framesrc_pm; }; struct error_message { diff --git a/apache2/msc_crypt.c b/apache2/msc_crypt.c new file mode 100644 index 00000000..5e4b6c04 --- /dev/null +++ b/apache2/msc_crypt.c @@ -0,0 +1,1369 @@ +/* + * ModSecurity for Apache 2.x, http://www.modsecurity.org/ + * Copyright (c) 2004-2011 Trustwave Holdings, Inc. (http://www.trustwave.com/) + * + * You may not use this file except in compliance with + * the License.  You may obtain a copy of the License at + * + *     http://www.apache.org/licenses/LICENSE-2.0 + * + * If any of the files related to licensing are missing or if you have any + * other questions related to licensing please contact Trustwave Holdings, Inc. + * directly using the email address security@modsecurity.org. + */ + +#include "msc_crypt.h" +#include "apr_sha1.h" +#include "apr_uri.h" +#include "acmp.h" +#include "libxml/HTMLtree.h" +#include "libxml/uri.h" + +/** + * \brief Normalize path in URI + * + * \param msr ModSecurity transaction resource + * \param input The URI to be normalized + * + * \retval input normalized input + * \retval NULL on fail + */ +char *normalize_path(modsec_rec *msr, char *input) { + xmlURI *uri = NULL; + char *parsed_content = NULL; + char *content = NULL; + + if(msr == NULL) return NULL; + + if(input == NULL) return NULL; + + uri = xmlParseURI(input); + + if(uri != NULL && uri->path) { + if(uri->scheme) { + content = apr_psprintf(msr->mp, "%s://", uri->scheme); + parsed_content = apr_pstrcat(msr->mp, content, NULL); + } + + if(uri->server) { + content = apr_psprintf(msr->mp, "%s", uri->server); + if(parsed_content) + parsed_content = apr_pstrcat(msr->mp, parsed_content, content, NULL); + else + parsed_content = apr_pstrcat(msr->mp, content, NULL); + } + + if(uri->port) { + content = apr_psprintf(msr->mp, ":%d", uri->port); + if(parsed_content) + parsed_content = apr_pstrcat(msr->mp, parsed_content, content, NULL); + else + parsed_content = apr_pstrcat(msr->mp, content, NULL); + } + + if(uri->path) { + char *Uri = NULL; + int bytes = 0; + int i; + + xmlNormalizeURIPath(uri->path); + Uri = apr_pstrdup(msr->mp, uri->path); + + for(i = 0; i < strlen(Uri); i++) { + if(Uri[i] != '.' && Uri[i] != '/') { + if (i - 1 < 0) + i = 0; + else + i--; + if(Uri[i] == '/') + --bytes; + break; + } else { + bytes++; + } + } + + if(bytes >= strlen(uri->path)) + return NULL; + + content = apr_psprintf(msr->mp, "%s", uri->path+bytes); + if(parsed_content) + parsed_content = apr_pstrcat(msr->mp, parsed_content, content, NULL); + else + parsed_content = apr_pstrcat(msr->mp, content, NULL); + } + + if(uri->query_raw) { + content = apr_psprintf(msr->mp, "?%s", uri->query_raw); + if(parsed_content) + parsed_content = apr_pstrcat(msr->mp, parsed_content, content, NULL); + else + parsed_content = apr_pstrcat(msr->mp, content, NULL); + } + + if(uri->fragment) { + content = apr_psprintf(msr->mp, "#%s", uri->fragment); + if(parsed_content) + parsed_content = apr_pstrcat(msr->mp, parsed_content, content, NULL); + else + parsed_content = apr_pstrcat(msr->mp, content, NULL); + } + xmlFreeURI(uri); + return apr_pstrdup(msr->mp, parsed_content); + } + + if(uri != NULL) xmlFreeURI(uri); + return apr_pstrdup(msr->mp, input); +} + +/** + * \brief Create a random password + * + * \param mp ModSecurity transaction memory pool + * + * \retval key random key + */ +unsigned char *getkey(apr_pool_t *mp) { + unsigned short int length = 12; + char output[13]; + char *key = NULL; + + output[length] = '\0'; + + srand((unsigned int) time(0)); + while(length--) { + output[length-1] = (rand() % 94 + 33); + srand(rand()); + } + + key = apr_psprintf(mp,"%s",output); + + return key; +} + +/** + * \brief Generate the MAC for a given message + * + * \param msr ModSecurity transaction resource + * \param key The key used within HMAC + * \param key_len Key length + * \param msg The message to generate the MAC + * \param msglen The message length + * + * \retval hex_digest The MAC + */ +char *hmac(modsec_rec *msr, const unsigned char *key, int key_len, + char *msg, int msglen) { + apr_sha1_ctx_t ctx; + unsigned char digest[APR_SHA1_DIGESTSIZE]; + unsigned char hmac_ipad[HMAC_PAD_SIZE], hmac_opad[HMAC_PAD_SIZE]; + unsigned char nkey[APR_SHA1_DIGESTSIZE]; + unsigned char *hmac_key = (unsigned char *) key; + char hex_digest[APR_SHA1_DIGESTSIZE * 2], *hmac_digest; + const char hex[] = "0123456789abcdef"; + int i; + + if (key_len > HMAC_PAD_SIZE-1) { + hmac_key = nkey; + key_len = APR_SHA1_DIGESTSIZE; + } + + memset ((void *) hmac_ipad, 0, sizeof (hmac_ipad)); + memset ((void *) hmac_opad, 0, sizeof (hmac_opad)); + memmove (hmac_ipad, hmac_key, key_len); + memmove (hmac_opad, hmac_key, key_len); + + for (i = 0; i < HMAC_PAD_SIZE-1; i++) { + hmac_ipad[i] ^= 0x36; + hmac_opad[i] ^= 0x5c; + } + + apr_sha1_init (&ctx); + apr_sha1_update_binary (&ctx, hmac_ipad, HMAC_PAD_SIZE-1); + apr_sha1_update_binary (&ctx, (const unsigned char *) msg, msglen); + apr_sha1_final (digest, &ctx); + + apr_sha1_init (&ctx); + apr_sha1_update_binary (&ctx, hmac_opad, HMAC_PAD_SIZE-1); + apr_sha1_update_binary (&ctx, digest, sizeof (digest)); + apr_sha1_final (digest, &ctx); + + hmac_digest = hex_digest; + for (i = 0; i < sizeof (digest); i++) { + *hmac_digest++ = hex[digest[i] >> 4]; + *hmac_digest++ = hex[digest[i] & 0xF]; + } + + *hmac_digest = '\0'; + + return apr_pstrdup (msr->mp, hex_digest); +} + + +/** + * \brief Init html response body parser + * + * \param msr ModSecurity transaction resource + * + * \retval 1 on success + * \retval -1 on fail + */ +int init_response_body_html_parser(modsec_rec *msr) { + char *charset = NULL; + char *final_charset = NULL; + char sep; + + if(msr == NULL) return -1; + + if(msr->r == NULL) return -1; + + if(msr->r->content_type == NULL) return -1; + + if(msr->crypto_html_tree != NULL){ + xmlFreeDoc(msr->crypto_html_tree); + msr->crypto_html_tree = NULL; + } + + if((strncmp("text/html",msr->r->content_type, 9) != 0)){ + if (msr->txcfg->debuglog_level >= 4) + msr_log(msr, 4, + "init_response_body_html_parser: skipping html_tree generation for Content[%s].", msr->r->content_type); + if(msr->crypto_html_tree != NULL){ + xmlFreeDoc(msr->crypto_html_tree); + msr->crypto_html_tree = NULL; + } + return -1; + } + + if (msr->resbody_length == 0) { + if (msr->txcfg->debuglog_level >= 4) + msr_log(msr, 4, "init_response_body_html_parser: skipping html_tree generation for zero length respomse body."); + msr->crypto_html_tree = NULL; + return 0; + } + + if((msr->r->content_encoding == NULL)||(apr_strnatcasecmp(msr->r->content_encoding,"(null)")==0)){ + charset=strcasestr(msr->r->content_type,"charset="); + if(charset == NULL){ + if (msr->txcfg->debuglog_level >= 4) + msr_log(msr, 4, "init_response_body_html_parser: assuming ISO-8859-1."); + msr->crypto_html_tree = htmlReadMemory(msr->resbody_data, msr->resbody_length, NULL, + "ISO-8859-1", HTML_PARSE_RECOVER | HTML_PARSE_NOBLANKS | HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING); + htmlSetMetaEncoding ((htmlDocPtr) msr->crypto_html_tree, "ISO-8859-1"); + } + else{ + charset+=8; + final_charset=strchr(charset,' '); + if(final_charset == NULL) final_charset = strchr(charset,';'); + if(final_charset != NULL) { + sep = *final_charset; + *final_charset = '\0'; + } + + if (msr->txcfg->debuglog_level >= 4) + msr_log(msr, 4, + "init_response_body_html_parser: Charset[%s]",charset); + msr->crypto_html_tree = htmlReadMemory(msr->resbody_data, msr->resbody_length, NULL, + charset, HTML_PARSE_RECOVER| HTML_PARSE_NOBLANKS | HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING); + htmlSetMetaEncoding ((htmlDocPtr) msr->crypto_html_tree, charset); + if(final_charset != NULL) *final_charset=sep; + } + + } + else{ + if (msr->txcfg->debuglog_level >= 4) + msr_log(msr, 4,"init_response_body_html_parser: Enconding[%s].",msr->r->content_encoding); + msr->crypto_html_tree = htmlReadMemory(msr->resbody_data, msr->resbody_length, NULL, + msr->r->content_encoding, HTML_PARSE_RECOVER | HTML_PARSE_NOBLANKS | HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING); + htmlSetMetaEncoding ((htmlDocPtr) msr->crypto_html_tree, msr->r->content_encoding); + } + if(msr->crypto_html_tree == NULL){ + if (msr->txcfg->debuglog_level >= 4) + msr_log(msr, 4, + "init_response_body_html_parser: Failed to parse response body."); + return -1; + } + else { + if (msr->txcfg->debuglog_level >= 4) + msr_log(msr, 4, + "init_response_body_html_parser: Successfully html parser generated."); + return 1; + } + + return 1; +} + +/** + * \brief Execute all encryption methods + * + * \param msr ModSecurity transaction resource + * \param link The html attr value to be checked + * \param type The encryption method type + * + * \retval 1 Match + * \retval 0 No Match + * \retval -1 on fail + */ +int do_encryption_method(modsec_rec *msr, char *link, int type) { + encryption_method **em = NULL; + int i = 0; + char *error_msg = NULL; + char *my_error_msg = NULL; + int ovector[33]; + int rc; + const char *ret; + + if(msr == NULL) return -1; + + em = (encryption_method **)msr->txcfg->encryption_method->elts; + + if(msr->txcfg->encryption_method->nelts == 0) + return 1; + + for (i = 0; i < msr->txcfg->encryption_method->nelts; i++) { + + if(em[i] != NULL && em[i]->param_data != NULL){ + + switch(type) { + case ENCRYPTION_URL_HREF_HASH_PM: + if(em[i]->type == ENCRYPTION_URL_HREF_HASH_PM) { + const char *match = NULL; + apr_status_t rc = 0; + ACMPT pt; + + pt.parser = (ACMP *)em[i]->param_data; + pt.ptr = NULL; + + rc = acmp_process_quick(&pt, &match, link, strlen(link)); + + if (rc) { + return 1; + } else { + return 0; + } + } + break; + case ENCRYPTION_URL_HREF_HASH_RX: + if(em[i]->type == ENCRYPTION_URL_HREF_HASH_RX) { + rc = msc_regexec_capture(em[i]->param_data, link, strlen(link), ovector, 30, &my_error_msg); + if ((rc == PCRE_ERROR_MATCHLIMIT) || (rc == PCRE_ERROR_RECURSIONLIMIT)) { + msc_string *s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); + + if (s == NULL) return -1; + s->name = apr_pstrdup(msr->mp, "MSC_PCRE_LIMITS_EXCEEDED"); + s->name_len = strlen(s->name); + s->value = apr_pstrdup(msr->mp, "1"); + s->value_len = 1; + if ((s->name == NULL)||(s->value == NULL)) return -1; + apr_table_setn(msr->tx_vars, s->name, (void *)s); + + error_msg = apr_psprintf(msr->mp, + "Execution error - " + "PCRE limits exceeded for Encrytion regex [%s] (%d): %s", + em[i]->param,rc, my_error_msg); + + if (msr->txcfg->debuglog_level >= 4) + msr_log(msr, 4, "%s.", error_msg); + + return 0; /* No match. */ + } + else if (rc < -1) { + error_msg = apr_psprintf(msr->mp, "Regex execution failed (%d): %s", + rc, my_error_msg); + return -1; + } + if (rc != PCRE_ERROR_NOMATCH) { /* Match. */ + return 1; + } + } + break; + case ENCRYPTION_URL_FACTION_HASH_PM: + if(em[i]->type == ENCRYPTION_URL_FACTION_HASH_PM) { + const char *match = NULL; + apr_status_t rc = 0; + ACMPT pt; + + pt.parser = (ACMP *)em[i]->param_data; + pt.ptr = NULL; + + rc = acmp_process_quick(&pt, &match, link, strlen(link)); + + if (rc) { + return 1; + } else { + return 0; + } + } + break; + case ENCRYPTION_URL_FACTION_HASH_RX: + if(em[i]->type == ENCRYPTION_URL_FACTION_HASH_RX) { + rc = msc_regexec_capture(em[i]->param_data, link, strlen(link), ovector, 30, &my_error_msg); + if ((rc == PCRE_ERROR_MATCHLIMIT) || (rc == PCRE_ERROR_RECURSIONLIMIT)) { + msc_string *s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); + + if (s == NULL) return -1; + s->name = apr_pstrdup(msr->mp, "MSC_PCRE_LIMITS_EXCEEDED"); + s->name_len = strlen(s->name); + s->value = apr_pstrdup(msr->mp, "1"); + s->value_len = 1; + if ((s->name == NULL)||(s->value == NULL)) return -1; + apr_table_setn(msr->tx_vars, s->name, (void *)s); + + error_msg = apr_psprintf(msr->mp, + "Execution error - " + "PCRE limits exceeded for Encrytion regex [%s] (%d): %s", + em[i]->param,rc, my_error_msg); + + if (msr->txcfg->debuglog_level >= 4) + msr_log(msr, 4, "%s.", error_msg); + + return 0; /* No match. */ + } + else if (rc < -1) { + error_msg = apr_psprintf(msr->mp, "Regex execution failed (%d): %s", + rc, my_error_msg); + return -1; + } + if (rc != PCRE_ERROR_NOMATCH) { /* Match. */ + return 1; + } + } + break; + case ENCRYPTION_URL_LOCATION_HASH_PM: + if(em[i]->type == ENCRYPTION_URL_LOCATION_HASH_PM) { + const char *match = NULL; + apr_status_t rc = 0; + ACMPT pt; + + pt.parser = (ACMP *)em[i]->param_data; + pt.ptr = NULL; + + rc = acmp_process_quick(&pt, &match, link, strlen(link)); + + if (rc) { + return 1; + } else { + return 0; + } + } + break; + case ENCRYPTION_URL_LOCATION_HASH_RX: + if(em[i]->type == ENCRYPTION_URL_LOCATION_HASH_RX) { + rc = msc_regexec_capture(em[i]->param_data, link, strlen(link), ovector, 30, &my_error_msg); + if ((rc == PCRE_ERROR_MATCHLIMIT) || (rc == PCRE_ERROR_RECURSIONLIMIT)) { + msc_string *s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); + + if (s == NULL) return -1; + s->name = apr_pstrdup(msr->mp, "MSC_PCRE_LIMITS_EXCEEDED"); + s->name_len = strlen(s->name); + s->value = apr_pstrdup(msr->mp, "1"); + s->value_len = 1; + if ((s->name == NULL)||(s->value == NULL)) return -1; + apr_table_setn(msr->tx_vars, s->name, (void *)s); + + error_msg = apr_psprintf(msr->mp, + "Execution error - " + "PCRE limits exceeded for Encrytion regex [%s] (%d): %s", + em[i]->param,rc, my_error_msg); + + if (msr->txcfg->debuglog_level >= 4) + msr_log(msr, 4, "%s.", error_msg); + + return 0; /* No match. */ + } + else if (rc < -1) { + error_msg = apr_psprintf(msr->mp, "Regex execution failed (%d): %s", + rc, my_error_msg); + return -1; + } + if (rc != PCRE_ERROR_NOMATCH) { /* Match. */ + return 1; + } + } + break; + case ENCRYPTION_URL_IFRAMESRC_HASH_PM: + if(em[i]->type == ENCRYPTION_URL_IFRAMESRC_HASH_PM) { + const char *match = NULL; + apr_status_t rc = 0; + ACMPT pt; + + pt.parser = (ACMP *)em[i]->param_data; + pt.ptr = NULL; + + rc = acmp_process_quick(&pt, &match, link, strlen(link)); + + if (rc) { + return 1; + } else { + return 0; + } + } + break; + case ENCRYPTION_URL_IFRAMESRC_HASH_RX: + if(em[i]->type == ENCRYPTION_URL_IFRAMESRC_HASH_RX) { + rc = msc_regexec_capture(em[i]->param_data, link, strlen(link), ovector, 30, &my_error_msg); + if ((rc == PCRE_ERROR_MATCHLIMIT) || (rc == PCRE_ERROR_RECURSIONLIMIT)) { + msc_string *s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); + + if (s == NULL) return -1; + s->name = apr_pstrdup(msr->mp, "MSC_PCRE_LIMITS_EXCEEDED"); + s->name_len = strlen(s->name); + s->value = apr_pstrdup(msr->mp, "1"); + s->value_len = 1; + if ((s->name == NULL)||(s->value == NULL)) return -1; + apr_table_setn(msr->tx_vars, s->name, (void *)s); + + error_msg = apr_psprintf(msr->mp, + "Execution error - " + "PCRE limits exceeded for Encrytion regex [%s] (%d): %s", + em[i]->param,rc, my_error_msg); + + if (msr->txcfg->debuglog_level >= 4) + msr_log(msr, 4, "%s.", error_msg); + + return 0; /* No match. */ + } + else if (rc < -1) { + error_msg = apr_psprintf(msr->mp, "Regex execution failed (%d): %s", + rc, my_error_msg); + return -1; + } + if (rc != PCRE_ERROR_NOMATCH) { /* Match. */ + return 1; + } + } + break; + case ENCRYPTION_URL_FRAMESRC_HASH_PM: + if(em[i]->type == ENCRYPTION_URL_FRAMESRC_HASH_PM) { + const char *match = NULL; + apr_status_t rc = 0; + ACMPT pt; + + pt.parser = (ACMP *)em[i]->param_data; + pt.ptr = NULL; + + rc = acmp_process_quick(&pt, &match, link, strlen(link)); + + if (rc) { + return 1; + } else { + return 0; + } + } + break; + case ENCRYPTION_URL_FRAMESRC_HASH_RX: + if(em[i]->type == ENCRYPTION_URL_FRAMESRC_HASH_RX) { + rc = msc_regexec_capture(em[i]->param_data, link, strlen(link), ovector, 30, &my_error_msg); + if ((rc == PCRE_ERROR_MATCHLIMIT) || (rc == PCRE_ERROR_RECURSIONLIMIT)) { + msc_string *s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); + + if (s == NULL) return -1; + s->name = apr_pstrdup(msr->mp, "MSC_PCRE_LIMITS_EXCEEDED"); + s->name_len = strlen(s->name); + s->value = apr_pstrdup(msr->mp, "1"); + s->value_len = 1; + if ((s->name == NULL)||(s->value == NULL)) return -1; + apr_table_setn(msr->tx_vars, s->name, (void *)s); + + error_msg = apr_psprintf(msr->mp, + "Execution error - " + "PCRE limits exceeded for Encrytion regex [%s] (%d): %s", + em[i]->param,rc, my_error_msg); + + if (msr->txcfg->debuglog_level >= 4) + msr_log(msr, 4, "%s.", error_msg); + + return 0; /* No match. */ + } + else if (rc < -1) { + error_msg = apr_psprintf(msr->mp, "Regex execution failed (%d): %s", + rc, my_error_msg); + return -1; + } + if (rc != PCRE_ERROR_NOMATCH) { /* Match. */ + return 1; + } + } + break; + } + } + + } + return 0; +} + +/** + * \brief Encrypt the html elements + * + * \param msr ModSecurity transaction resource + * + * \retval 1 On success + * \retval 0 No element was changed + * \retval -1 On fail + */ +int encrypt_response_body_links(modsec_rec *msr) { + int lsize = 0, fsize = 0, lcount = 0, fcount = 0, i; + int isize = 0, icount = 0, frsize = 0, frcount = 0; + xmlXPathContextPtr xpathCtx = NULL; + xmlXPathObjectPtr xpathObj = NULL; + xmlChar *content_option = NULL; + char *mac_link = NULL; + int rc, elts = 0; + + if(msr == NULL) + return -1; + + if (msr->crypto_html_tree == NULL) { + if (msr->txcfg->debuglog_level >= 4) + msr_log(msr, 4, "encrypt_response_body_links: Cannot parse NULL html tree"); + return -1; + } + + if(msr->txcfg->crypto_hash_href_rx == 0 && msr->txcfg->crypto_hash_href_pm == 0 + && msr->txcfg->crypto_hash_faction_rx == 0 && msr->txcfg->crypto_hash_faction_pm == 0 + && msr->txcfg->crypto_hash_iframesrc_rx == 0 && msr->txcfg->crypto_hash_iframesrc_pm == 0 + && msr->txcfg->crypto_hash_framesrc_rx == 0 && msr->txcfg->crypto_hash_framesrc_pm == 0) + return -1; + + xpathCtx = xmlXPathNewContext(msr->crypto_html_tree); + if(xpathCtx == NULL) { + if (msr->txcfg->debuglog_level >= 4) + msr_log(msr, 4, "encrypt_response_body_links: Unable to create Xpath context."); + goto ctx_error; + } + + lcount=fcount=0; + + if(msr->txcfg->crypto_hash_href_rx == 1 || msr->txcfg->crypto_hash_href_pm == 1) { + + xpathObj = xmlXPathEvalExpression((xmlChar*)"//*[@href]", xpathCtx); + if(xpathObj == NULL) { + if (msr->txcfg->debuglog_level >= 4) + msr_log(msr, 4, + "encrypt_response_body_links: Unable to evaluate xpath expression."); + goto obj_error; + } + + lsize = (xpathObj->nodesetval) ? xpathObj->nodesetval->nodeNr : 0; + for(i = lsize - 1; i >=0; i--) { + register xmlNodePtr cur; + + cur = xpathObj->nodesetval->nodeTab[i]; + if(cur != NULL){ + xmlChar *href = xmlGetProp(cur, (const xmlChar *) "href"); + char *content_href = normalize_path(msr, (char *)href); + + if(content_href != NULL && strstr(content_href,msr->txcfg->crypto_param_name) == NULL) { + if(msr->txcfg->crypto_hash_href_rx == 1) { + rc = do_encryption_method(msr, (char *)content_href, ENCRYPTION_URL_HREF_HASH_RX); + if(rc > 0) { + mac_link = NULL; + mac_link = do_hash_link(msr, (char *)content_href, FULL_LINK); + if(mac_link != NULL) { + xmlSetProp(cur, (const xmlChar *) "href", (const xmlChar *) mac_link); + lcount++; + msr->of_stream_changed = 1; + } + mac_link = NULL; + if(href != NULL) + xmlFree(href); + continue; + } + } + if(msr->txcfg->crypto_hash_href_pm == 1) { + rc = do_encryption_method(msr, (char *)content_href, ENCRYPTION_URL_HREF_HASH_PM); + if(rc > 0) { + mac_link = NULL; + mac_link = do_hash_link(msr, (char *)content_href, FULL_LINK); + if(mac_link != NULL) { + xmlSetProp(cur, (const xmlChar *) "href", (const xmlChar *) mac_link); + lcount++; + msr->of_stream_changed = 1; + } + mac_link = NULL; + if(href != NULL) + xmlFree(href); + continue; + } + } + } + + if(href != NULL) { + xmlFree(href); + href = NULL; + } + } + } + + if(xpathObj != NULL) + xmlXPathFreeObject(xpathObj); + } + + if(msr->txcfg->crypto_hash_faction_rx == 1 || msr->txcfg->crypto_hash_faction_pm == 1) { + xpathObj = xmlXPathEvalExpression((xmlChar*)"//form", xpathCtx); + if(xpathObj == NULL) { + if (msr->txcfg->debuglog_level >= 4) + msr_log(msr, 4, + "encrypt_response_body_links: Unable to evaluate xpath expression."); + goto obj_error; + } + + fsize = (xpathObj->nodesetval) ? xpathObj->nodesetval->nodeNr : 0; + for(i = fsize - 1; i >=0; i--) { + register xmlNodePtr cur; + + cur = xpathObj->nodesetval->nodeTab[i]; + if((cur != NULL)){ + xmlChar *action = NULL; + char *content_action = NULL; + + if(content_option) + xmlFree(content_option); + + action = xmlGetProp(cur, (const xmlChar *) "action"); + content_action = normalize_path(msr, (char *)action); + content_option = xmlGetProp(cur, (const xmlChar *) "option"); + + if(content_action != NULL && content_option == NULL && strstr(content_action,msr->txcfg->crypto_param_name) == NULL) { + if(msr->txcfg->crypto_hash_faction_rx == 1) { + rc = do_encryption_method(msr, (char *)content_action, ENCRYPTION_URL_FACTION_HASH_RX); + if(rc > 0) { + mac_link = NULL; + mac_link = do_hash_link(msr, (char *)content_action, FULL_LINK); + if(mac_link != NULL) { + xmlSetProp(cur, (const xmlChar *) "action", (const xmlChar *) mac_link); + fcount++; + msr->of_stream_changed = 1; + } + mac_link = NULL; + if(action != NULL) + xmlFree(action); + continue; + } + } + if(msr->txcfg->crypto_hash_faction_pm == 1) { + rc = do_encryption_method(msr, (char *)content_action, ENCRYPTION_URL_FACTION_HASH_PM); + if(rc > 0) { + mac_link = NULL; + mac_link = do_hash_link(msr, (char *)content_action, FULL_LINK); + if(mac_link != NULL) { + xmlSetProp(cur, (const xmlChar *) "action", (const xmlChar *) mac_link); + fcount++; + msr->of_stream_changed = 1; + } + mac_link = NULL; + if(action != NULL) + xmlFree(action); + continue; + } + } + } + + if(action != NULL) { + xmlFree(action); + action = NULL; + } + + if(content_option) { + xmlFree(content_option); + content_option = NULL; + } + } + } + + if(xpathObj != NULL) + xmlXPathFreeObject(xpathObj); + } + + if(msr->txcfg->crypto_hash_iframesrc_rx == 1 || msr->txcfg->crypto_hash_iframesrc_pm == 1) { + xpathObj = xmlXPathEvalExpression((xmlChar*)"//iframe", xpathCtx); + if(xpathObj == NULL) { + if (msr->txcfg->debuglog_level >= 4) + msr_log(msr, 4, + "encrypt_response_body_links: Unable to evaluate xpath expression."); + goto obj_error; + } + + isize = (xpathObj->nodesetval) ? xpathObj->nodesetval->nodeNr : 0; + for(i = isize - 1; i >=0; i--) { + register xmlNodePtr cur; + + cur = xpathObj->nodesetval->nodeTab[i]; + if((cur != NULL)){ + + xmlChar *src = xmlGetProp(cur, (const xmlChar *) "src"); + char *content_src = normalize_path(msr, (char *)src); + + if(content_src != NULL && strstr(content_src,msr->txcfg->crypto_param_name) == NULL) { + if(msr->txcfg->crypto_hash_iframesrc_rx == 1) { + rc = do_encryption_method(msr, (char *)content_src, ENCRYPTION_URL_IFRAMESRC_HASH_RX); + if(rc > 0) { + mac_link = NULL; + mac_link = do_hash_link(msr, (char *)content_src, FULL_LINK); + if(mac_link != NULL) { + xmlSetProp(cur, (const xmlChar *) "src", (const xmlChar *) mac_link); + icount++; + msr->of_stream_changed = 1; + } + mac_link = NULL; + if(src != NULL) + xmlFree(src); + continue; + } + } + if(msr->txcfg->crypto_hash_iframesrc_pm == 1) { + rc = do_encryption_method(msr, (char *)content_src, ENCRYPTION_URL_IFRAMESRC_HASH_PM); + if(rc > 0) { + mac_link = NULL; + mac_link = do_hash_link(msr, (char *)content_src, FULL_LINK); + if(mac_link != NULL) { + xmlSetProp(cur, (const xmlChar *) "src", (const xmlChar *) mac_link); + icount++; + msr->of_stream_changed = 1; + } + mac_link = NULL; + if(src != NULL) + xmlFree(src); + continue; + } + } + } + + if(src != NULL) { + xmlFree(src); + src = NULL; + } + } + } + + if(xpathObj != NULL) + xmlXPathFreeObject(xpathObj); + } + + if(msr->txcfg->crypto_hash_framesrc_rx == 1 || msr->txcfg->crypto_hash_framesrc_pm == 1) { + xpathObj = xmlXPathEvalExpression((xmlChar*)"//frame", xpathCtx); + if(xpathObj == NULL) { + if (msr->txcfg->debuglog_level >= 4) + msr_log(msr, 4, + "encrypt_response_body_links: Unable to evaluate xpath expression."); + goto obj_error; + } + + frsize = (xpathObj->nodesetval) ? xpathObj->nodesetval->nodeNr : 0; + for(i = frsize - 1; i >=0; i--) { + register xmlNodePtr cur; + + cur = xpathObj->nodesetval->nodeTab[i]; + if((cur != NULL)){ + + xmlChar *src = xmlGetProp(cur, (const xmlChar *) "src"); + char *content_src = normalize_path(msr, (char *)src); + + if(content_src != NULL && strstr(content_src,msr->txcfg->crypto_param_name) == NULL) { + if(msr->txcfg->crypto_hash_framesrc_rx == 1) { + rc = do_encryption_method(msr, (char *)content_src, ENCRYPTION_URL_FRAMESRC_HASH_RX); + if(rc > 0) { + mac_link = NULL; + mac_link = do_hash_link(msr, (char *)content_src, FULL_LINK); + if(mac_link != NULL) { + xmlSetProp(cur, (const xmlChar *) "src", (const xmlChar *) mac_link); + frcount++; + msr->of_stream_changed = 1; + } + mac_link = NULL; + if(src != NULL) + xmlFree(src); + continue; + } + } + if(msr->txcfg->crypto_hash_framesrc_pm == 1) { + rc = do_encryption_method(msr, (char *)content_src, ENCRYPTION_URL_FRAMESRC_HASH_PM); + if(rc > 0) { + mac_link = NULL; + mac_link = do_hash_link(msr, (char *)content_src, FULL_LINK); + if(mac_link != NULL) { + xmlSetProp(cur, (const xmlChar *) "src", (const xmlChar *) mac_link); + frcount++; + msr->of_stream_changed = 1; + } + mac_link = NULL; + if(src != NULL) + xmlFree(src); + continue; + } + } + } + + if(src != NULL) { + xmlFree(src); + src = NULL; + } + } + } + + if(xpathObj != NULL) + xmlXPathFreeObject(xpathObj); + } + + if(xpathCtx != NULL) + xmlXPathFreeContext(xpathCtx); + + if (msr->txcfg->debuglog_level >= 4) { + msr_log(msr, 4, "encrypt_response_body_links: Processed [%d] iframe src, [%d] encrypted.",isize, icount); + msr_log(msr, 4, "encrypt_response_body_links: Processed [%d] frame src, [%d] encrypted.",frsize, frcount); + msr_log(msr, 4, "encrypt_response_body_links: Processed [%d] form actions, [%d] encrypted.",fsize, fcount); + msr_log(msr, 4, "encrypt_response_body_links: Processed [%d] links, [%d] encrypted.",lsize, lcount); + } + + if(msr->of_stream_changed == 0) { + if(msr->crypto_html_tree != NULL) { + xmlFreeDoc(msr->crypto_html_tree); + msr->crypto_html_tree = NULL; + } + return 0; + } + + elts = (icount+frcount+fcount+lcount); + + if((elts >= INT32_MAX) || (elts < 0)) + return 0; + + return elts; + +obj_error: + if(xpathCtx != NULL) + xmlXPathFreeContext(xpathCtx); +ctx_error: + return -1; +} + +/** + * \brief Inject the new response body + * + * \param msr ModSecurity transaction resource + * \param elts Number of encrypted elements + * + * \retval 1 On success + * \retval -1 On fail + */ +int inject_encrypted_response_body(modsec_rec *msr, int elts) { + xmlOutputBufferPtr output_buf = NULL; + xmlCharEncodingHandlerPtr handler = NULL; + char *p = NULL; + const char *ctype = NULL; + const char *encoding = NULL; + char* new_ct = NULL; + int rc = 0; + + if(msr == NULL) return -1; + + if(msr->r == NULL) return -1; + + if (msr->crypto_html_tree == NULL) { + if (msr->txcfg->debuglog_level >= 4) + msr_log(msr, 4, "inject_encrypted_response_body: Cannot parse NULL html tree"); + return -1; + } + + if(msr->r->content_type != NULL) + ctype = msr->r->content_type; + + encoding = (const char *) htmlGetMetaEncoding(msr->crypto_html_tree); + + if (ctype && encoding == NULL) { + if (ctype && (p = strcasestr(ctype, "charset=") , p != NULL)) { + p += 8 ; + if (encoding = apr_pstrndup(msr->mp, p, strcspn(p, " ;") ), encoding) { + xmlCharEncoding enc; + enc = xmlParseCharEncoding(encoding); + handler = xmlFindCharEncodingHandler(encoding); + } + } + } else { + if(encoding != NULL) { + xmlCharEncoding enc; + enc = xmlParseCharEncoding(encoding); + handler = xmlFindCharEncodingHandler(encoding); + } + } + + if (msr->txcfg->debuglog_level >= 4) + msr_log(msr, 4, "inject_encrypted_response_body: Detected encoding type [%s].", encoding); + + if (handler == NULL) + handler = xmlFindCharEncodingHandler("UTF-8"); + if (handler == NULL) + handler = xmlFindCharEncodingHandler("ISO-8859-1"); + if (handler == NULL) + handler = xmlFindCharEncodingHandler("HTML"); + if (handler == NULL) + handler = xmlFindCharEncodingHandler("ascii"); + + if(handler == NULL) { + xmlFreeDoc(msr->crypto_html_tree); + return -1; + } + + apr_table_unset(msr->r->headers_out,"Content-Type"); + new_ct = (char*)apr_psprintf(msr->mp, "text/html;%s",handler->name); + apr_table_set(msr->r->err_headers_out,"Content-Type",new_ct); + + if (msr->txcfg->debuglog_level >= 4) + msr_log(msr, 4, "inject_encrypted_response_body: Using content-type [%s].", handler->name); + + output_buf = xmlAllocOutputBuffer(handler); + if (output_buf == NULL) { + if (msr->txcfg->debuglog_level >= 4) + msr_log(msr, 4, "inject_encrypted_response_body: Unable to allocate memory buffer."); + xmlFreeDoc(msr->crypto_html_tree); + return -1; + } + + htmlDocContentDumpFormatOutput(output_buf, msr->crypto_html_tree, NULL, 0); + + if (output_buf->conv == NULL || (output_buf->conv && output_buf->conv->use == 0)) { + + if(output_buf->buffer == NULL || output_buf->buffer->use == 0) { + xmlOutputBufferClose(output_buf); + xmlFreeDoc(msr->crypto_html_tree); + msr->of_stream_changed = 0; + return -1; + } + + if(msr->stream_output_data != NULL) { + free(msr->stream_output_data); + msr->stream_output_data = NULL; + } + + msr->stream_output_length = output_buf->buffer->use; + msr->stream_output_data = (unsigned char *)malloc(msr->stream_output_length+1); + + if (msr->stream_output_data == NULL) { + xmlOutputBufferClose(output_buf); + xmlFreeDoc(msr->crypto_html_tree); + return -1; + } + + memset(msr->stream_output_data, 0x0, msr->stream_output_length+1); + memcpy(msr->stream_output_data, output_buf->buffer->content, msr->stream_output_length); + + if (msr->txcfg->debuglog_level >= 4) + msr_log(msr, 4, "inject_encrypted_response_body: Copying XML tree from CONTENT to stream buffer [%d] bytes.", output_buf->buffer->use); + + } else { + + if(output_buf->conv == NULL || output_buf->conv->use == 0) { + xmlOutputBufferClose(output_buf); + xmlFreeDoc(msr->crypto_html_tree); + msr->of_stream_changed = 0; + return -1; + } + + if(msr->stream_output_data != NULL) { + free(msr->stream_output_data); + msr->stream_output_data = NULL; + } + + msr->stream_output_length = output_buf->conv->use; + msr->stream_output_data = (unsigned char *)malloc(msr->stream_output_length+1); + + if (msr->stream_output_data == NULL) { + xmlOutputBufferClose(output_buf); + xmlFreeDoc(msr->crypto_html_tree); + return -1; + } + + memset(msr->stream_output_data, 0x0, msr->stream_output_length+1); + memcpy(msr->stream_output_data, output_buf->conv->content, msr->stream_output_length); + + if (msr->txcfg->debuglog_level >= 4) + msr_log(msr, 4, "inject_encrypted_response_body: Copying XML tree from CONV to stream buffer [%d] bytes.", output_buf->conv->use); + + } + + xmlOutputBufferClose(output_buf); + + apr_table_unset(msr->r->headers_out,"Content-Length"); + apr_table_set(msr->r->headers_out, "Content-Length",(char*)apr_psprintf(msr->mp, APR_SIZE_T_FMT, msr->stream_output_length)); + + xmlFreeDoc(msr->crypto_html_tree); + + if (msr->txcfg->debuglog_level >= 4) + msr_log(msr, 4, "inject_encrypted_response_body: Stream buffer [%"APR_SIZE_T_FMT"]. Done",msr->stream_output_length); + + return 1; +} + +/** + * \brief Parse and MAC html elements + * + * \param msr ModSecurity transaction resource + * \param link The html attr value to be checked + * \param type The encryption method type + * + * \retval mac_link MACed link + * \retval NULL on fail + */ +unsigned char *do_hash_link(modsec_rec *msr, char *link, int type) { + unsigned char *mac_link = NULL; + char *path_chunk = NULL; + unsigned char *hash_value = NULL; + char *qm = NULL; + + if(msr == NULL) return NULL; + + if(strlen(link) > 7 && strncmp("http:",(char*)link,5)==0){ + path_chunk = strchr(link+7,'/'); + if(path_chunk != NULL) { + if (msr->txcfg->debuglog_level >= 4) + msr_log(msr, 4, "Signing data [%s]", path_chunk+1); + + if(msr->txcfg->crypto_key_add == ENCRYPTION_KEYONLY) + hash_value = hmac(msr, msr->txcfg->crypto_key, msr->txcfg->crypto_key_len, (unsigned char *) path_chunk+1, strlen((char*)path_chunk)-1); + + if(msr->txcfg->crypto_key_add == ENCRYPTION_SESSIONID) { + if(strlen(msr->sessionid) == 0) { +#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER > 2 + const char *new_pwd = apr_psprintf(msr->mp,"%s%s", msr->txcfg->crypto_key, msr->r->connection->client_ip); +#else + const char *new_pwd = apr_psprintf(msr->mp,"%s%s", msr->txcfg->crypto_key, msr->r->connection->remote_ip); +#endif + if (msr->txcfg->debuglog_level >= 4) + msr_log(msr, 4, "Session id is empty. Using REMOTE_IP"); + msr->txcfg->crypto_key_len = strlen(new_pwd); + hash_value = hmac(msr, new_pwd, msr->txcfg->crypto_key_len, (unsigned char *) path_chunk+1, strlen((char*)path_chunk)-1); + } else { + const char *new_pwd = apr_psprintf(msr->mp,"%s%s", msr->txcfg->crypto_key, msr->sessionid); + if (msr->txcfg->debuglog_level >= 4) + msr_log(msr, 4, "Using session id [%s]", msr->sessionid); + msr->txcfg->crypto_key_len = strlen(new_pwd); + hash_value = hmac(msr, new_pwd, msr->txcfg->crypto_key_len, (unsigned char *) path_chunk+1, strlen((char*)path_chunk)-1); + } + } + + if(msr->txcfg->crypto_key_add == ENCRYPTION_REMOTEIP) { +#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER > 2 + const char *new_pwd = apr_psprintf(msr->mp,"%s%s", msr->txcfg->crypto_key, msr->r->connection->client_ip); +#else + const char *new_pwd = apr_psprintf(msr->mp,"%s%s", msr->txcfg->crypto_key, msr->r->connection->remote_ip); +#endif + msr->txcfg->crypto_key_len = strlen(new_pwd); + hash_value = hmac(msr, new_pwd, msr->txcfg->crypto_key_len, (unsigned char *) path_chunk+1, strlen((char*)path_chunk)-1); + } + } else { + return NULL; + } + } else + if(strlen(link) > 8 && strncmp("https",(char*)link,5)==0){ + path_chunk = strchr(link+8,'/'); + if(path_chunk != NULL) { + if (msr->txcfg->debuglog_level >= 4) + msr_log(msr, 4, "Signing data [%s]", path_chunk+1); + + if(msr->txcfg->crypto_key_add == ENCRYPTION_KEYONLY) + hash_value = hmac(msr, msr->txcfg->crypto_key, msr->txcfg->crypto_key_len, (unsigned char *) path_chunk+1, strlen((char*)path_chunk)-1); + + if(msr->txcfg->crypto_key_add == ENCRYPTION_SESSIONID) { + if(strlen(msr->sessionid) == 0) { +#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER > 2 + const char *new_pwd = apr_psprintf(msr->mp,"%s%s", msr->txcfg->crypto_key, msr->r->connection->client_ip); +#else + const char *new_pwd = apr_psprintf(msr->mp,"%s%s", msr->txcfg->crypto_key, msr->r->connection->remote_ip); +#endif + if (msr->txcfg->debuglog_level >= 4) + msr_log(msr, 4, "Session id is empty. Using REMOTE_IP"); + msr->txcfg->crypto_key_len = strlen(new_pwd); + hash_value = hmac(msr, new_pwd, msr->txcfg->crypto_key_len, (unsigned char *) path_chunk+1, strlen((char*)path_chunk)-1); + } else { + const char *new_pwd = apr_psprintf(msr->mp,"%s%s", msr->txcfg->crypto_key, msr->sessionid); + if (msr->txcfg->debuglog_level >= 4) + msr_log(msr, 4, "Using session id [%s]", msr->sessionid); + msr->txcfg->crypto_key_len = strlen(new_pwd); + hash_value = hmac(msr, new_pwd, msr->txcfg->crypto_key_len, (unsigned char *) path_chunk+1, strlen((char*)path_chunk)-1); + } + } + + if(msr->txcfg->crypto_key_add == ENCRYPTION_REMOTEIP) { +#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER > 2 + const char *new_pwd = apr_psprintf(msr->mp,"%s%s", msr->txcfg->crypto_key, msr->r->connection->client_ip); +#else + const char *new_pwd = apr_psprintf(msr->mp,"%s%s", msr->txcfg->crypto_key, msr->r->connection->remote_ip); +#endif + msr->txcfg->crypto_key_len = strlen(new_pwd); + hash_value = hmac(msr, new_pwd, msr->txcfg->crypto_key_len, (unsigned char *) path_chunk+1, strlen((char*)path_chunk)-1); + } + } else { + return NULL; + } + } + else if(*link=='/'){ + if (msr->txcfg->debuglog_level >= 4) + msr_log(msr, 4, "Signing data [%s]", link+1); + + if(msr->txcfg->crypto_key_add == ENCRYPTION_KEYONLY) + hash_value = hmac(msr, msr->txcfg->crypto_key, msr->txcfg->crypto_key_len, (unsigned char *) link+1, strlen((char*)link)-1); + + if(msr->txcfg->crypto_key_add == ENCRYPTION_SESSIONID) { + if(strlen(msr->sessionid) == 0) { +#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER > 2 + const char *new_pwd = apr_psprintf(msr->mp,"%s%s", msr->txcfg->crypto_key, msr->r->connection->client_ip); +#else + const char *new_pwd = apr_psprintf(msr->mp,"%s%s", msr->txcfg->crypto_key, msr->r->connection->remote_ip); +#endif + if (msr->txcfg->debuglog_level >= 4) + msr_log(msr, 4, "Session id is empty. Using REMOTE_IP"); + msr->txcfg->crypto_key_len = strlen(new_pwd); + hash_value = hmac(msr, new_pwd, msr->txcfg->crypto_key_len, (unsigned char *) link+1, strlen((char*)link)-1); + } else { + const char *new_pwd = apr_psprintf(msr->mp,"%s%s", msr->txcfg->crypto_key, msr->sessionid); + if (msr->txcfg->debuglog_level >= 4) + msr_log(msr, 4, "Using session id [%s]", msr->sessionid); + msr->txcfg->crypto_key_len = strlen(new_pwd); + hash_value = hmac(msr, new_pwd, msr->txcfg->crypto_key_len, (unsigned char *) link+1, strlen((char*)link)-1); + } + } + + if(msr->txcfg->crypto_key_add == ENCRYPTION_REMOTEIP) { +#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER > 2 + const char *new_pwd = apr_psprintf(msr->mp,"%s%s", msr->txcfg->crypto_key, msr->r->connection->client_ip); +#else + const char *new_pwd = apr_psprintf(msr->mp,"%s%s", msr->txcfg->crypto_key, msr->r->connection->remote_ip); +#endif + msr->txcfg->crypto_key_len = strlen(new_pwd); + hash_value = hmac(msr, new_pwd, msr->txcfg->crypto_key_len, (unsigned char *) link+1, strlen((char*)link)-1); + } + + } + else { + char *relative_link = NULL; + char *filename = NULL; + char *relative_path = NULL; + char *relative_uri = NULL; + + filename = file_basename(msr->mp, msr->r->parsed_uri.path); + relative_path = apr_pstrndup(msr->mp, msr->r->parsed_uri.path, strlen(msr->r->parsed_uri.path) - strlen(filename)); + relative_uri = apr_pstrcat(msr->mp, relative_path, link, NULL); + + relative_link = relative_uri+1; + + if (msr->txcfg->debuglog_level >= 4) + msr_log(msr, 4, "Signing data [%s] size %d", relative_link, strlen(relative_link)); + + if(msr->txcfg->crypto_key_add == ENCRYPTION_KEYONLY) + hash_value = hmac(msr, msr->txcfg->crypto_key, msr->txcfg->crypto_key_len, (unsigned char *) relative_link, strlen((char*)relative_link)); + + if(msr->txcfg->crypto_key_add == ENCRYPTION_SESSIONID) { + if(strlen(msr->sessionid) == 0) { +#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER > 2 + const char *new_pwd = apr_psprintf(msr->mp,"%s%s", msr->txcfg->crypto_key, msr->r->connection->client_ip); +#else + const char *new_pwd = apr_psprintf(msr->mp,"%s%s", msr->txcfg->crypto_key, msr->r->connection->remote_ip); +#endif + if (msr->txcfg->debuglog_level >= 4) + msr_log(msr, 4, "Session id is empty. Using REMOTE_IP"); + msr->txcfg->crypto_key_len = strlen(new_pwd); + hash_value = hmac(msr, new_pwd, msr->txcfg->crypto_key_len, (unsigned char *) relative_link, strlen((char*)relative_link)); + } else { + const char *new_pwd = apr_psprintf(msr->mp,"%s%s", msr->txcfg->crypto_key, msr->sessionid); + if (msr->txcfg->debuglog_level >= 4) + msr_log(msr, 4, "Using session id [%s]", msr->sessionid); + msr->txcfg->crypto_key_len = strlen(new_pwd); + hash_value = hmac(msr, new_pwd, msr->txcfg->crypto_key_len, (unsigned char *) relative_link, strlen((char*)relative_link)); + } + } + + if(msr->txcfg->crypto_key_add == ENCRYPTION_REMOTEIP) { +#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER > 2 + const char *new_pwd = apr_psprintf(msr->mp,"%s%s", msr->txcfg->crypto_key, msr->r->connection->client_ip); +#else + const char *new_pwd = apr_psprintf(msr->mp,"%s%s", msr->txcfg->crypto_key, msr->r->connection->remote_ip); +#endif + msr->txcfg->crypto_key_len = strlen(new_pwd); + hash_value = hmac(msr, new_pwd, msr->txcfg->crypto_key_len, (unsigned char *) relative_link, strlen((char*)relative_link)); + } + } + + if(hash_value == NULL) return NULL; + + if(type == HASH_ONLY) + return hash_value; + + qm = strchr((char*)link,'?'); + if(qm == NULL){ + mac_link= (unsigned char*)apr_psprintf(msr->mp, "%s?%s=%s", link, msr->txcfg->crypto_param_name, (char *)hash_value); + } + else{ + mac_link= (unsigned char*)apr_psprintf(msr->mp, "%s&%s=%s", link, msr->txcfg->crypto_param_name, (char*)hash_value); + } + + return mac_link; +} + +/** + * \brief Modify Location in case of status 302 and 301 + * + * \param msr ModSecurity transaction resource + * + * \retval 1 On Success + * \retval 0 on fail + */ +int modify_response_header(modsec_rec *msr) { + char *mac_link = NULL; + const char *location = NULL; + int rc = 0; + + if(msr == NULL) return 0; + + if (msr->txcfg->debuglog_level >= 9) + msr_log(msr, 4, "HTTP status (%d)", msr->response_status); + + if(msr->response_status != HTTP_MOVED_TEMPORARILY && + msr->response_status != HTTP_MOVED_PERMANENTLY) { + if (msr->txcfg->debuglog_level >= 9) + msr_log(msr, 4, "Skipping status other than 302 an 301"); + return 0; + } + + location = apr_table_get(msr->r->headers_out, "Location"); + + if(location == NULL || strlen(location) == 0) + return 0; + + if (msr->txcfg->debuglog_level >= 9) + msr_log(msr, 4, "Processing reponse header location [%s]", location); + + if(msr->txcfg->crypto_hash_location_rx == 1) { + rc = do_encryption_method(msr, (char *)location, ENCRYPTION_URL_LOCATION_HASH_RX); + + if(rc > 0) { + mac_link = NULL; + mac_link = do_hash_link(msr, (char *)location, FULL_LINK); + } else { + return 0; + } + + } else if(msr->txcfg->crypto_hash_location_pm == 1) { + rc = do_encryption_method(msr, (char *)location, ENCRYPTION_URL_LOCATION_HASH_PM); + + if(rc > 0) { + mac_link = NULL; + mac_link = do_hash_link(msr, (char *)location, FULL_LINK); + } else { + return 0; + } + + } + + if(mac_link == NULL) + return 0; + + if (msr->txcfg->debuglog_level >= 9) + msr_log(msr, 4, "Setting new reponse header location [%s]", mac_link); + + if(rc > 0) { + apr_table_unset(msr->r->headers_out,"Location"); + apr_table_set(msr->r->headers_out, "Location",(char*)apr_psprintf(msr->mp,"%s", mac_link)); + } + + return 1; +} diff --git a/apache2/msc_crypt.h b/apache2/msc_crypt.h new file mode 100644 index 00000000..c5f6e8d8 --- /dev/null +++ b/apache2/msc_crypt.h @@ -0,0 +1,42 @@ +/* +* ModSecurity for Apache 2.x, http://www.modsecurity.org/ +* Copyright (c) 2004-2011 Trustwave Holdings, Inc. (http://www.trustwave.com/) +* +* You may not use this file except in compliance with +* the License.  You may obtain a copy of the License at +* +*     http://www.apache.org/licenses/LICENSE-2.0 +* +* If any of the files related to licensing are missing or if you have any +* other questions related to licensing please contact Trustwave Holdings, Inc. +* directly using the email address security@modsecurity.org. +*/ + +#ifndef _MSC_CRYPT_H_ +#define _MSC_CRYPT_H_ + +#include "modsecurity.h" +#include +#include + +#define HMAC_PAD_SIZE 65 +#define HASH_ONLY 0 +#define FULL_LINK 1 + +#ifndef INT32_MAX +#define INT32_MAX (2147483647) +#endif + +char DSOLOCAL *hmac(modsec_rec *msr,const unsigned char *key, int key_len, + char *msg, int msglen); +unsigned char DSOLOCAL *do_hash_link(modsec_rec *msr, char *link, + int type); +unsigned char DSOLOCAL *getkey(apr_pool_t *mp); + +int DSOLOCAL init_response_body_html_parser(modsec_rec *msr); +int DSOLOCAL encrypt_response_body_links(modsec_rec *msr); +int DSOLOCAL inject_encrypted_response_body(modsec_rec *msr, int elts); +int DSOLOCAL do_encryption_method(modsec_rec *msr, char *link, int type); +int DSOLOCAL modify_response_header(modsec_rec *msr); +char DSOLOCAL *normalize_path(modsec_rec *msr, char *input); +#endif diff --git a/apache2/msc_gsb.c b/apache2/msc_gsb.c index 77c34e92..d3449e2f 100644 --- a/apache2/msc_gsb.c +++ b/apache2/msc_gsb.c @@ -14,6 +14,14 @@ #include "msc_gsb.h" +/** \brief Load GSB database + * + * \param dcfg Pointer to directory configuration + * \param error_msg Error message + * + * \retval 1 On Success + * \retval 0 On Fail + */ static int gsb_db_create(directory_config *dcfg, char **error_msg) { char errstr[1024]; @@ -89,8 +97,14 @@ static int gsb_db_create(directory_config *dcfg, char **error_msg) } -/** - * Initialise Gsb malware data structure +/** \brief Init GSB database + * + * \param dcfg Pointer to directory configuration + * \param dbfn Database filename + * \param error_msg Error message + * + * \retval gsb_db_create On Success + * \retval -1 On Fail */ int gsb_db_init(directory_config *dcfg, const char *dbfn, char **error_msg) { diff --git a/apache2/msc_logging.c b/apache2/msc_logging.c index 4770ccf4..896a3dd5 100644 --- a/apache2/msc_logging.c +++ b/apache2/msc_logging.c @@ -1050,6 +1050,41 @@ void sec_audit_logger(modsec_rec *msr) { msr->userid == NULL ? "-" : log_escape(msr->mp, msr->userid)); sec_auditlog_write(msr, text, strlen(text)); } + + if ( ((msr->txcfg->sensor_id != NULL)&&(strcmp(msr->txcfg->sensor_id, "default") != 0))) + { + text = apr_psprintf(msr->mp, "Sensor-Id: \"%s\"\n", + msr->txcfg->sensor_id == NULL ? "-" : log_escape(msr->mp, msr->txcfg->sensor_id)), + sec_auditlog_write(msr, text, strlen(text)); + } + + + if (msr->txcfg->is_enabled > 0) { + text = apr_psprintf(msr->mp, "Engine-Mode: \"%s\"\n", + msr->txcfg->is_enabled == 1 ? "DETECTION_ONLY" : "ENABLED"), + sec_auditlog_write(msr, text, strlen(text)); + } + + /* Rule performance time */ + if(msr->txcfg->max_rule_time > 0) { + const apr_array_header_t *tarr; + const apr_table_entry_t *telts; + + tarr = apr_table_elts(msr->perf_rules); + telts = (const apr_table_entry_t*)tarr->elts; + + if (tarr->nelts > 0) { + text = apr_psprintf(msr->mp, "Rules-Performance-Info: "); + sec_auditlog_write(msr, text, strlen(text)); + } + + for(i = 0; i < tarr->nelts; i++) { + text = apr_psprintf(msr->mp, "%s\"%s=%s\"%s", ((i == 0) ? "" : ", "), + log_escape(msr->mp, telts[i].key), log_escape(msr->mp, telts[i].val), ((i == (tarr->nelts - 1)) ? ".\n" : "")); + sec_auditlog_write(msr, text, strlen(text)); + } + } + } /* AUDITLOG_PART_UPLOADS */ diff --git a/apache2/msc_lua.c b/apache2/msc_lua.c index 494727db..db8d01b6 100644 --- a/apache2/msc_lua.c +++ b/apache2/msc_lua.c @@ -393,7 +393,7 @@ static const struct luaL_Reg mylib[] = { int lua_execute(msc_script *script, char *param, modsec_rec *msr, msre_rule *rule, char **error_msg) { apr_time_t time_before; lua_State *L = NULL; - int rc; + int rc = 0; if (error_msg == NULL) return -1; *error_msg = NULL; @@ -404,10 +404,19 @@ int lua_execute(msc_script *script, char *param, modsec_rec *msr, msre_rule *rul time_before = apr_time_now(); +#ifdef CACHE_LUA + L = msr->L; + rc = lua_gettop(L); + if(rc) + lua_pop(L, rc); +#else /* Create new state. */ L = lua_open(); - luaL_openlibs(L); +#endif + + if(L == NULL) + return -1; /* Associate msr with the state. */ lua_pushlightuserdata(L, (void *)msr); @@ -441,6 +450,11 @@ int lua_execute(msc_script *script, char *param, modsec_rec *msr, msre_rule *rul if (lua_pcall(L, ((param != NULL) ? 1 : 0), 1, 0)) { *error_msg = apr_psprintf(msr->mp, "Lua: Script execution failed: %s", lua_tostring(L, -1)); + + if (msr->txcfg->debuglog_level >= 8) { + msr_log(msr, 8, "Lua: Script execution failed: %s", lua_tostring(L, -1)); + } + return -1; } @@ -452,7 +466,9 @@ int lua_execute(msc_script *script, char *param, modsec_rec *msr, msre_rule *rul /* Destroy state. */ lua_pop(L, 1); +#ifndef CACHE_LUA lua_close(L); +#endif /* Returns status code to caller. */ if (msr->txcfg->debuglog_level >= 8) { diff --git a/apache2/msc_release.h b/apache2/msc_release.h index 9f57d13f..b99585fc 100644 --- a/apache2/msc_release.h +++ b/apache2/msc_release.h @@ -39,8 +39,8 @@ #define MODSEC_VERSION_MAJOR "2" #define MODSEC_VERSION_MINOR "7" #define MODSEC_VERSION_MAINT "0" -#define MODSEC_VERSION_TYPE "trunk" -#define MODSEC_VERSION_RELEASE "" +#define MODSEC_VERSION_TYPE "-rc" +#define MODSEC_VERSION_RELEASE "1" #define MODSEC_VERSION_SUFFIX MODSEC_VERSION_TYPE MODSEC_VERSION_RELEASE diff --git a/apache2/msc_unicode.c b/apache2/msc_unicode.c index 46f6e0c4..1090efcf 100644 --- a/apache2/msc_unicode.c +++ b/apache2/msc_unicode.c @@ -16,6 +16,14 @@ #define CODEPAGE_SEPARATORS " \t\n\r" +/** \brief Load Unicode file + * + * \param dcfg Pointer to directory configuration + * \param error_msg Error message + * + * \retval 1 On Success + * \retval 0 On Fail + */ static int unicode_map_create(directory_config *dcfg, char **error_msg) { char errstr[1024]; @@ -129,8 +137,14 @@ static int unicode_map_create(directory_config *dcfg, char **error_msg) } -/** - * Initialise Unicode Map data structure +/** \brief Init unicode map + * + * \param dcfg Pointer to directory configuration + * \param mapfn Unicode map filename + * \param error_msg Error message + * + * \retval unicode_map_create On Success + * \retval -1 On Fail */ int unicode_map_init(directory_config *dcfg, const char *mapfn, char **error_msg) { diff --git a/apache2/msc_util.c b/apache2/msc_util.c index c452e18e..58ebd70d 100644 --- a/apache2/msc_util.c +++ b/apache2/msc_util.c @@ -74,7 +74,151 @@ static unsigned char *c2x(unsigned what, unsigned char *where); static unsigned char x2c(unsigned char *what); static unsigned char xsingle2c(unsigned char *what); -/* \brief Remove escape char +/** \brief Interpret |HEX| syntax + * + * \param op_parm Pointer to operator input + * \param op_len Operator input lenght + * \param rule Pointer to rule struct + * \param error_msg Pointer to error message + * + * \retval string On Success + */ +char *parse_pm_content(const char *op_parm, unsigned short int op_len, msre_rule *rule, char **error_msg) { + char *parm = NULL; + char *content = NULL; + unsigned short int offset = 0; + char converted = 0; + int i, x; + unsigned char bin = 0, esc = 0, bin_offset = 0; + unsigned char bin_parm[3], c = 0; + char *processed = NULL; + + content = apr_pstrdup(rule->ruleset->mp, op_parm); + + if (content == NULL) { + *error_msg = apr_psprintf(rule->ruleset->mp, "Error allocating memory for pattern matching content."); + return NULL; + } + + while (offset < op_len && apr_isspace(content[offset])) { + offset++; + }; + + op_len = strlen(content); + + if (content[offset] == '\"' && content[op_len-1] == '\"') { + parm = apr_pstrdup(rule->ruleset->mp, content + offset + 1); + if (parm == NULL) { + *error_msg = apr_psprintf(rule->ruleset->mp, "Error allocating memory for pattern matching content."); + return NULL; + } + parm[op_len - offset - 2] = '\0'; + } else { + parm = apr_pstrdup(rule->ruleset->mp, content + offset); + if (parm == NULL) { + *error_msg = apr_psprintf(rule->ruleset->mp, "Error allocating memory for pattern matching content."); + return NULL; + } + } + + op_len = strlen(parm); + + if (op_len == 0) { + *error_msg = apr_psprintf(rule->ruleset->mp, "Content length is 0."); + return NULL; + } + + + for (i = 0, x = 0; i < op_len; i++) { + if (parm[i] == '|') { + if (bin) { + bin = 0; + } else { + bin = 1; + } + } else if(!esc && parm[i] == '\\') { + esc = 1; + } else { + if (bin) { + if (apr_isdigit(parm[i]) || + parm[i] == 'A' || parm[i] == 'a' || + parm[i] == 'B' || parm[i] == 'b' || + parm[i] == 'C' || parm[i] == 'c' || + parm[i] == 'D' || parm[i] == 'd' || + parm[i] == 'E' || parm[i] == 'e' || + parm[i] == 'F' || parm[i] == 'f') + { + bin_parm[bin_offset] = (char)parm[i]; + bin_offset++; + if (bin_offset == 2) { + c = strtol((char *)bin_parm, (char **) NULL, 16) & 0xFF; + bin_offset = 0; + parm[x] = c; + x++; + converted = 1; + } + } else if (parm[i] == ' ') { + } + } else if (esc) { + if (parm[i] == ':' || + parm[i] == ';' || + parm[i] == '\\' || + parm[i] == '\"') + { + parm[x] = parm[i]; + x++; + } else { + *error_msg = apr_psprintf(rule->ruleset->mp, "Unsupported escape sequence."); + return NULL; + } + esc = 0; + converted = 1; + } else { + parm[x] = parm[i]; + x++; + } + } + } + + if (converted) { + op_len = x; + } + + processed = apr_pstrmemdup(rule->ruleset->mp, parm, op_len); + + if (processed == NULL) { + *error_msg = apr_psprintf(rule->ruleset->mp, "Error allocating memory for pattern matching content."); + return NULL; + } + + return processed; +} + + +/** \brief Remove quotes + * + * \param mptmp Pointer to the pool + * \param input Pointer to input string + * \param input_len Input data length + * + * \retval string On Success + */ +char *remove_quotes(apr_pool_t *mptmp, const char *input, int input_len) { + char *parm = apr_palloc(mptmp, input_len); + char *ret = parm; + int len = input_len; + + for(; *input !='\0' && len >=0; input++, len--) { + if(*input != '\'' && *input != '\"') { + *parm++ = *input; + } + } + + *parm = '\0'; + return ret; +} + +/** \brief Remove escape char * * \param mptmp Pointer to the pool * \param input Pointer to input string @@ -112,7 +256,7 @@ int parse_boolean(const char *input) { return -1; } -/* \brief Decode Base64 data with special chars +/** \brief Decode Base64 data with special chars * * \param plain_text Pointer to plain text data * \param input Pointer to input data @@ -121,8 +265,7 @@ int parse_boolean(const char *input) { * \retval 0 On failure * \retval string length On Success */ -int decode_base64_ext(char *plain_text, const unsigned char *input, int input_len) -{ +int decode_base64_ext(char *plain_text, const unsigned char *input, int input_len) { const unsigned char *encoded = input; int i = 0, j = 0, k = 0; int ch = 0; @@ -177,13 +320,14 @@ int decode_base64_ext(char *plain_text, const unsigned char *input, int input_le return j; } -/* \brief Convert const char to int +/** \brief Convert const char to int * * \param c number string * * \retval n The converted number */ -int convert_to_int(const char c) { +int convert_to_int(const char c) +{ int n; if ((c>='0') && (c<='9')) n = c - '0'; @@ -196,7 +340,7 @@ int convert_to_int(const char c) { return n; } -/* \brief Set a match to tx.N +/** \brief Set a match to tx.N * * \param msr Pointer to modsec resource * \param capture If ON match will be saved @@ -223,6 +367,7 @@ int set_match_to_tx(modsec_rec *msr, int capture, const char *match, int tx_n) msr_log(msr, 9, "Added phrase match to TX.%d: %s", tx_n, log_escape_nq_ex(msr->mp, s->value, s->value_len)); } + } return 0; @@ -437,7 +582,7 @@ int sql_hex2bytes_inplace(unsigned char *data, int len) { } *d = '\0'; - return strlen(begin); + return strlen((char *)begin); } /** @@ -632,13 +777,13 @@ char *current_filetime(apr_pool_t *mp) { /** * */ -int msc_mkstemp_ex(char *template, int mode) { +int msc_mkstemp_ex(char *templat, int mode) { int fd = -1; /* ENH Use apr_file_mktemp instead. */ #if !(defined(WIN32)||defined(NETWARE)) - fd = mkstemp(template); + fd = mkstemp(templat); #ifdef HAVE_FCHMOD if ((fd != -1) && (mode != 0)) { if (fchmod(fd, mode) == -1) { @@ -647,8 +792,8 @@ int msc_mkstemp_ex(char *template, int mode) { } #endif /* HAVE_FCHMOD */ #else - if (mktemp(template) == NULL) return -1; - fd = open(template, O_WRONLY | O_APPEND | O_CREAT | O_BINARY, mode); + if (mktemp(templat) == NULL) return -1; + fd = open(templat, O_WRONLY | O_APPEND | O_CREAT | O_BINARY, mode); #endif /* !(defined(WIN32)||defined(NETWARE)) */ return fd; @@ -657,8 +802,8 @@ int msc_mkstemp_ex(char *template, int mode) { /** * */ -int msc_mkstemp(char *template) { - return msc_mkstemp_ex(template, CREATEMODE_UNISTD); +int msc_mkstemp(char *templat) { + return msc_mkstemp_ex(templat, CREATEMODE_UNISTD); } /** diff --git a/apache2/msc_util.h b/apache2/msc_util.h index 9ebc80e3..7ca2eb28 100644 --- a/apache2/msc_util.h +++ b/apache2/msc_util.h @@ -26,11 +26,16 @@ #endif #include "modsecurity.h" +#include "re.h" int DSOLOCAL normalize_path_inplace(unsigned char *input, int len, int win, int *changed); int DSOLOCAL parse_boolean(const char *input); +char DSOLOCAL *remove_quotes(apr_pool_t *mptmp, const char *input, int input_len); + +char DSOLOCAL *parse_pm_content(const char *op_parm, unsigned short int op_len, msre_rule *rule, char **error_msg); + char DSOLOCAL *remove_escape(apr_pool_t *mptmp, const char *input, int input_len); int DSOLOCAL parse_name_eq_value(apr_pool_t *mp, const char *input, char **name, char **value); @@ -59,9 +64,9 @@ char DSOLOCAL *current_logtime(apr_pool_t *mp); char DSOLOCAL *current_filetime(apr_pool_t *mp); -int DSOLOCAL msc_mkstemp_ex(char *template, int mode); +int DSOLOCAL msc_mkstemp_ex(char *templat, int mode); -int DSOLOCAL msc_mkstemp(char *template); +int DSOLOCAL msc_mkstemp(char *templat); char DSOLOCAL *strtolower_inplace(unsigned char *str); diff --git a/apache2/persist_dbm.c b/apache2/persist_dbm.c index 39982390..215ebcda 100644 --- a/apache2/persist_dbm.c +++ b/apache2/persist_dbm.c @@ -108,10 +108,12 @@ static apr_table_t *collection_retrieve_ex(apr_sdbm_t *existing_dbm, modsec_rec goto cleanup; } - if(strstr(col_name,"USER") || strstr(col_name,"SESSION")) - dbm_filename = apr_pstrcat(msr->mp, msr->txcfg->data_dir, "/", msr->txcfg->webappid, "_", col_name, NULL); - else - dbm_filename = apr_pstrcat(msr->mp, msr->txcfg->data_dir, "/", col_name, NULL); + dbm_filename = apr_pstrcat(msr->mp, msr->txcfg->data_dir, "/", col_name, NULL); + + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "collection_retrieve_ex: Retrieving collection (name \"%s\", filename \"%s\")",log_escape(msr->mp, col_name), + log_escape(msr->mp, dbm_filename)); + } key.dptr = (char *)col_key; key.dsize = col_key_len + 1; @@ -353,6 +355,11 @@ int collection_store(modsec_rec *msr, apr_table_t *col) { // ENH: lowercase the var name in the filename dbm_filename = apr_pstrcat(msr->mp, msr->txcfg->data_dir, "/", var_name->value, NULL); + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "collection_store: Retrieving collection (name \"%s\", filename \"%s\")",log_escape(msr->mp, var_name->value), + log_escape(msr->mp, dbm_filename)); + } + /* Delete IS_NEW on store. */ apr_table_unset(col, "IS_NEW"); @@ -579,16 +586,21 @@ int collections_remove_stale(modsec_rec *msr, const char *col_name) { goto error; } - if(strstr(col_name,"USER") || strstr(col_name,"SESSION")) + if(strcasestr(col_name,"user") || strcasestr(col_name,"session") || strcasestr(col_name,"resource")) dbm_filename = apr_pstrcat(msr->mp, msr->txcfg->data_dir, "/", msr->txcfg->webappid, "_", col_name, NULL); else dbm_filename = apr_pstrcat(msr->mp, msr->txcfg->data_dir, "/", col_name, NULL); + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "collections_remove_stale: Retrieving collection (name \"%s\", filename \"%s\")",log_escape(msr->mp, col_name), + log_escape(msr->mp, dbm_filename)); + } + rc = apr_sdbm_open(&dbm, dbm_filename, APR_CREATE | APR_WRITE | APR_SHARELOCK, - CREATEMODE, msr->mp); + CREATEMODE, msr->mp); if (rc != APR_SUCCESS) { msr_log(msr, 1, "Failed to access DBM file \"%s\": %s", log_escape(msr->mp, dbm_filename), - get_apr_error(msr->mp, rc)); + get_apr_error(msr->mp, rc)); dbm = NULL; goto error; } diff --git a/apache2/re.c b/apache2/re.c index 351dd3df..b71302ce 100644 --- a/apache2/re.c +++ b/apache2/re.c @@ -42,147 +42,137 @@ static msre_action *msre_create_action(msre_engine *engine, const char *name, static apr_status_t msre_rule_process(msre_rule *rule, modsec_rec *msr); /* -- Actions, variables, functions and operator functions ----------------- */ +/** + * \brief Update target for all matching rules in set, in any phase + * + * \param msr ModSecurity transaction resource + * \param ruleset Pointer to set of rules to modify + * \param re Pointer to exception object describing which rules to modify + * \param p2 Pointer to configuration option TARGET + * \param p3 Pointer to configuration option REPLACED_TARGET + */ +char *msre_ruleset_rule_update_target_matching_exception(modsec_rec *msr, msre_ruleset *ruleset, rule_exception *re, const char *p2, const char *p3) { + char *err; -char *update_rule_target(cmd_parms *cmd, directory_config *dcfg, - msre_ruleset *rset, const char *p1, const char *p2, const char *p3) + if(ruleset == NULL) + return apr_psprintf(ruleset->mp, "No ruleset present"); + + if(p2 == NULL) { + return apr_psprintf(ruleset->mp, "Trying to update without a target"); + } + + if (NULL != (err = msre_ruleset_phase_rule_update_target_matching_exception(msr, ruleset, re, ruleset->phase_request_headers, p2, p3))) + return err; + if (NULL != (err = msre_ruleset_phase_rule_update_target_matching_exception(msr, ruleset, re, ruleset->phase_request_body, p2, p3))) + return err; + if (NULL != (err = msre_ruleset_phase_rule_update_target_matching_exception(msr, ruleset, re, ruleset->phase_response_headers, p2, p3))) + return err; + if (NULL != (err = msre_ruleset_phase_rule_update_target_matching_exception(msr, ruleset, re, ruleset->phase_response_body, p2, p3))) + return err; + if (NULL != (err = msre_ruleset_phase_rule_update_target_matching_exception(msr, ruleset, re, ruleset->phase_logging, p2, p3))) + return err; + + /* Everything worked! */ + return NULL; +} + + +/** + * \brief Update target for all matching rules in set for a specific phase + * + * \param msr ModSecurity transaction resource + * \param ruleset Pointer to set of rules to modify + * \param re Pointer to exception object describing which rules to modify + * \param phase_arr Pointer to phase that should be edited + * \param p2 Pointer to configuration option TARGET + * \param p3 Pointer to configuration option REPLACED_TARGET + * + * \todo Figure out error checking + */ +char *msre_ruleset_phase_rule_update_target_matching_exception(modsec_rec *msr, msre_ruleset *ruleset, rule_exception *re, + apr_array_header_t *phase_arr, const char *p2, + const char *p3) { + msre_rule **rules; + int i, j, mode; + char *err; + + j = 0; + mode = 0; + rules = (msre_rule **)phase_arr->elts; + for (i = 0; i < phase_arr->nelts; i++) { + msre_rule *rule = (msre_rule *)rules[i]; + + if (mode == 0) { /* Looking for next rule. */ + if (msre_ruleset_rule_matches_exception(rule, re)) { + + err = update_rule_target_ex(NULL, ruleset, rule, p2, p3); + if (err) return err; + if (rule->actionset->is_chained) mode = 2; /* Match all rules in this chain. */ + } else { + if (rule->actionset->is_chained) mode = 1; /* Skip all rules in this chain. */ + } + } else { /* Handling rule that is part of a chain. */ + if (mode == 2) { /* We want to change the rule. */ + err = update_rule_target_ex(msr, ruleset, rule, p2, p3); + if (err) return err; + } else { + rules[j++] = rules[i]; + } + + if ((rule->actionset == NULL)||(rule->actionset->is_chained == 0)) mode = 0; + } + } + + return NULL; +} + + +char *update_rule_target_ex(modsec_rec *msr, msre_ruleset *ruleset, msre_rule *rule, const char *p2, + const char *p3) { + msre_var **targets = NULL; - msre_rule *rule = NULL; - msre_ruleset *ruleset = NULL; - const char *curr_targets = NULL; + const char *current_targets = NULL; char *my_error_msg = NULL, *target = NULL; char *p = NULL, *savedptr = NULL; - char *target_list = NULL, *replace = NULL; unsigned int is_negated = 0, is_counting = 0; int name_len = 0, value_len = 0; char *name = NULL, *value = NULL; char *opt = NULL, *param = NULL; + char *target_list = NULL, *replace = NULL; int i, rc, match = 0; - int offset = 0; - if(p1 == NULL || p2 == NULL || (dcfg == NULL && rset == NULL)) { - return NULL; - } + if(rule != NULL) { - if(dcfg != NULL) - ruleset = dcfg->ruleset; - else if (rset != NULL) - ruleset = rset; - - /* Get the ruleset if one exists */ - if ((ruleset == NULL)||(ruleset == NOT_SET_P)) { - return NULL; - } - rule = msre_ruleset_fetch_rule(ruleset, p1, offset); - if (rule == NULL) { - return NULL; - } - - target_list = strdup(p2); - if(target_list == NULL) - return NULL; - - if(p3 != NULL) { - replace = strdup(p3); - if(replace == NULL) { - free(target_list); - target_list = NULL; - return NULL; - } - } - - if(replace != NULL) { - - opt = strchr(replace,'!'); - - if(opt != NULL) { - *opt = '\0'; - opt++; - param = opt; - is_negated = 1; - } else if ((opt = strchr(replace,'&')) != NULL) { - *opt = '\0'; - opt++; - param = opt; - is_counting = 1; - } else { - param = replace; - } - - opt = strchr(param,':'); - - if(opt != NULL) { - name = apr_strtok(param,":",&value); - } else { - name = param; - } - - name_len = strlen(name); - - if(value != NULL) - value_len = strlen(value); - - targets = (msre_var **)rule->targets->elts; - // TODO need a good way to remove the element from array, maybe change array by tables or rings - for (i = 0; i < rule->targets->nelts; i++) { - if((strlen(targets[i]->name) == strlen(name)) && - (strncasecmp(targets[i]->name,name,strlen(targets[i]->name)) == 0) && - (targets[i]->is_negated == is_negated) && - (targets[i]->is_counting == is_counting)) { - - if(value != NULL && targets[i]->param != NULL) { - if((strlen(targets[i]->param) == strlen(value)) && - strncasecmp(targets[i]->param,value,strlen(targets[i]->param)) == 0) { - memset(targets[i]->name,0,strlen(targets[i]->name)); - memset(targets[i]->param,0,strlen(targets[i]->param)); - match = 1; - } - } else if (value == NULL && targets[i]->param == NULL){ - memset(targets[i]->name,0,strlen(targets[i]->name)); - match = 1; - } else - continue; + target_list = strdup(p2); + if(target_list == NULL) + return apr_psprintf(ruleset->mp, "Error to update target - memory allocation");; + if(p3 != NULL) { + replace = strdup(p3); + if(replace == NULL) { + free(target_list); + target_list = NULL; + return apr_psprintf(ruleset->mp, "Error to update target - memory allocation");; } } - } - - p = apr_strtok(target_list, ",", &savedptr); - - while(p != NULL) { if(replace != NULL) { - if(match == 1) { - rc = msre_parse_targets(ruleset, p, rule->targets, &my_error_msg); - if (rc < 0) { - goto end; - } - } else { - goto end; - } - } else { - target = strdup(p); - if(target == NULL) - return NULL; - - is_negated = is_counting = 0; - param = name = value = NULL; - - opt = strchr(target,'!'); + opt = strchr(replace,'!'); if(opt != NULL) { *opt = '\0'; opt++; param = opt; is_negated = 1; - } else if ((opt = strchr(target,'&')) != NULL) { + } else if ((opt = strchr(replace,'&')) != NULL) { *opt = '\0'; opt++; param = opt; is_counting = 1; } else { - param = target; + param = replace; } opt = strchr(param,':'); @@ -193,14 +183,33 @@ char *update_rule_target(cmd_parms *cmd, directory_config *dcfg, name = param; } + if(apr_table_get(ruleset->engine->variables, name) == NULL) { + if(target_list != NULL) + free(target_list); + if(replace != NULL) + free(replace); + if(msr) { + msr_log(msr, 9, "Error to update target - [%s] is not valid target", name); + } + return apr_psprintf(ruleset->mp, "Error to update target - [%s] is not valid target", name); + } + name_len = strlen(name); if(value != NULL) value_len = strlen(value); - match = 0; + if(msr) { + msr_log(msr, 9, "Trying to replace by variable name [%s] value [%s]", name, value); + } +#if !defined(MSC_TEST) + else { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL, " ModSecurity: Trying to replace by variable name [%s] value [%s]", name, value); + } +#endif targets = (msre_var **)rule->targets->elts; + // TODO need a good way to remove the element from array, maybe change array by tables or rings for (i = 0; i < rule->targets->nelts; i++) { if((strlen(targets[i]->name) == strlen(name)) && (strncasecmp(targets[i]->name,name,strlen(targets[i]->name)) == 0) && @@ -209,48 +218,258 @@ char *update_rule_target(cmd_parms *cmd, directory_config *dcfg, if(value != NULL && targets[i]->param != NULL) { if((strlen(targets[i]->param) == strlen(value)) && - strncasecmp(targets[i]->param,value,strlen(targets[i]->param)) == 0) { + strncasecmp(targets[i]->param,value,strlen(targets[i]->param)) == 0) { + memset(targets[i]->name,0,strlen(targets[i]->name)); + memset(targets[i]->param,0,strlen(targets[i]->param)); match = 1; } } else if (value == NULL && targets[i]->param == NULL){ + memset(targets[i]->name,0,strlen(targets[i]->name)); match = 1; } else continue; } } - - if(target != NULL) { - free(target); - target = NULL; - } - - - if(match == 0 ) { - rc = msre_parse_targets(ruleset, p, rule->targets, &my_error_msg); - if (rc < 0) { - goto end; - } - } } - p = apr_strtok(NULL,",",&savedptr); + p = apr_strtok(target_list, ",", &savedptr); + + while(p != NULL) { + if(replace != NULL) { + if(match == 1) { + rc = msre_parse_targets(ruleset, p, rule->targets, &my_error_msg); + if (rc < 0) { + if(msr) { + msr_log(msr, 9, "Error parsing rule targets to replace variable"); + } +#if !defined(MSC_TEST) + else { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, " ModSecurity: Error parseing rule targets to replace variable"); + } +#endif + goto end; + } + if(msr) { + msr_log(msr, 9, "Successfuly replaced variable"); + } +#if !defined(MSC_TEST) + else { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL, " ModSecurity: Successfuly replaced variable"); + } +#endif + } else { + if(msr) { + msr_log(msr, 9, "Cannot find variable to replace"); + } +#if !defined(MSC_TEST) + else { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, " ModSecurity: Cannot find varibale to replace"); + } +#endif + goto end; + } + } else { + + target = strdup(p); + if(target == NULL) + return NULL; + + is_negated = is_counting = 0; + param = name = value = NULL; + + opt = strchr(target,'!'); + + if(opt != NULL) { + *opt = '\0'; + opt++; + param = opt; + is_negated = 1; + } else if ((opt = strchr(target,'&')) != NULL) { + *opt = '\0'; + opt++; + param = opt; + is_counting = 1; + } else { + param = target; + } + + opt = strchr(param,':'); + + if(opt != NULL) { + name = apr_strtok(param,":",&value); + } else { + name = param; + } + + if(apr_table_get(ruleset->engine->variables, name) == NULL) { + if(target_list != NULL) + free(target_list); + if(replace != NULL) + free(replace); + if(msr) { + msr_log(msr, 9, "Error to update target - [%s] is not valid target", name); + } + return apr_psprintf(ruleset->mp, "Error to update target - [%s] is not valid target", name); + } + + name_len = strlen(name); + + if(value != NULL) + value_len = strlen(value); + + if(msr) { + msr_log(msr, 9, "Trying to append variable name [%s] value [%s]", name, value); + } +#if !defined(MSC_TEST) + else { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL, " ModSecurity: Trying to append variable name [%s] value [%s]", name, value); + } +#endif + match = 0; + + targets = (msre_var **)rule->targets->elts; + for (i = 0; i < rule->targets->nelts; i++) { + if((strlen(targets[i]->name) == strlen(name)) && + (strncasecmp(targets[i]->name,name,strlen(targets[i]->name)) == 0) && + (targets[i]->is_negated == is_negated) && + (targets[i]->is_counting == is_counting)) { + + if(value != NULL && targets[i]->param != NULL) { + if((strlen(targets[i]->param) == strlen(value)) && + strncasecmp(targets[i]->param,value,strlen(targets[i]->param)) == 0) { + match = 1; + } + } else if (value == NULL && targets[i]->param == NULL){ + match = 1; + } else + continue; + + } + } + + if(target != NULL) { + free(target); + target = NULL; + } + + if(match == 0 ) { + rc = msre_parse_targets(ruleset, p, rule->targets, &my_error_msg); + if (rc < 0) { + if(msr) { + msr_log(msr, 9, "Error parsing rule targets to append variable"); + } +#if !defined(MSC_TEST) + else { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, " ModSecurity: Error parseing rule targets to append variable"); + } +#endif + goto end; + } + } else { + if(msr) { + msr_log(msr, 9, "Skipping variable, already appended"); + } +#if !defined(MSC_TEST) + else { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL, " ModSecurity: Skipping variable, already appended"); + } +#endif + } + } + + p = apr_strtok(NULL,",",&savedptr); + } + + if(match == 0) { + current_targets = msre_generate_target_string(ruleset->mp, rule); + rule->unparsed = msre_rule_generate_unparsed(ruleset->mp, rule, current_targets, NULL, NULL); + if(msr) { + msr_log(msr, 9, "Successfuly appended variable"); + } +#if !defined(MSC_TEST) + else { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL, " ModSecurity: Successfuly appended variable"); + } +#endif + } } - curr_targets = msre_generate_target_string(ruleset->mp, rule); - - rule->unparsed = msre_rule_generate_unparsed(ruleset->mp, rule, curr_targets, NULL, NULL); - end: - if(target_list != NULL) + if(target_list != NULL) { free(target_list); - if(replace != NULL) + target_list = NULL; + } + if(replace != NULL) { free(replace); - if(target != NULL) + replace = NULL; + } + if(target != NULL) { free(target); + target = NULL; + } return NULL; } +int msre_ruleset_rule_matches_exception(msre_rule *rule, rule_exception *re) { + int match = 0; + + /* Only remove non-placeholder rules */ + if (rule->placeholder == RULE_PH_NONE) { + switch(re->type) { + case RULE_EXCEPTION_REMOVE_ID : + if ((rule->actionset != NULL)&&(rule->actionset->id != NULL)) { + int ruleid = atoi(rule->actionset->id); + + if (rule_id_in_range(ruleid, re->param)) { + match = 1; + } + } + + break; + case RULE_EXCEPTION_REMOVE_MSG : + if ((rule->actionset != NULL)&&(rule->actionset->msg != NULL)) { + char *my_error_msg = NULL; + + int rc = msc_regexec(re->param_data, + rule->actionset->msg, strlen(rule->actionset->msg), + &my_error_msg); + if (rc >= 0) { + match = 1; + } + } + + break; + case RULE_EXCEPTION_REMOVE_TAG : + if ((rule->actionset != NULL)&&(apr_is_empty_table(rule->actionset->actions) == 0)) { + char *my_error_msg = NULL; + const apr_array_header_t *tarr = NULL; + const apr_table_entry_t *telts = NULL; + int act; + + tarr = apr_table_elts(rule->actionset->actions); + telts = (const apr_table_entry_t*)tarr->elts; + + for (act = 0; act < tarr->nelts; act++) { + msre_action *action = (msre_action *)telts[act].val; + if((action != NULL) && (action->metadata != NULL) && (strcmp("tag", action->metadata->name) == 0)) { + + int rc = msc_regexec(re->param_data, + action->param, strlen(action->param), + &my_error_msg); + if (rc >= 0) { + match = 1; + } + } + } + } + break; + } + } + return match; +} + + /** * Remove actions with the same cardinality group from the actionset. @@ -824,7 +1043,7 @@ msre_actionset *msre_actionset_create(msre_engine *engine, const char *text, actionset->intercept_action = NOT_SET; actionset->intercept_uri = NOT_SET_P; actionset->intercept_status = NOT_SET; - actionset->intercept_pause = NOT_SET; + actionset->intercept_pause = NOT_SET_P; /* Other */ actionset->auditlog = NOT_SET; @@ -907,7 +1126,7 @@ msre_actionset *msre_actionset_merge(msre_engine *engine, msre_actionset *parent } if (child->intercept_status != NOT_SET) merged->intercept_status = child->intercept_status; - if (child->intercept_pause != NOT_SET) merged->intercept_pause = child->intercept_pause; + if (child->intercept_pause != NOT_SET_P) merged->intercept_pause = child->intercept_pause; /* Other */ if (child->auditlog != NOT_SET) merged->auditlog = child->auditlog; @@ -962,7 +1181,7 @@ void msre_actionset_set_defaults(msre_actionset *actionset) { if (actionset->intercept_action == NOT_SET) actionset->intercept_action = ACTION_NONE; if (actionset->intercept_uri == NOT_SET_P) actionset->intercept_uri = NULL; if (actionset->intercept_status == NOT_SET) actionset->intercept_status = 403; - if (actionset->intercept_pause == NOT_SET) actionset->intercept_pause = 0; + if (actionset->intercept_pause == NOT_SET_P) actionset->intercept_pause = NULL; /* Other */ if (actionset->auditlog == NOT_SET) actionset->auditlog = 1; @@ -1222,13 +1441,35 @@ static apr_status_t msre_ruleset_process_phase_(msre_ruleset *ruleset, modsec_re } /* Check if this rule was removed at runtime */ - if (((rule->actionset->id !=NULL) && !apr_is_empty_array(msr->removed_rules)) || (apr_is_empty_array(msr->removed_rules_tag)==0)) { - int j, act; + if (((rule->actionset->id !=NULL) && !apr_is_empty_array(msr->removed_rules)) || + (apr_is_empty_array(msr->removed_rules_tag)==0) || (apr_is_empty_array(msr->removed_rules_msg)==0)) { + int j, act, rc; int do_process = 1; - const char *range; + const char *range = NULL; + rule_exception *re = NULL; + char *my_error_msg, *error_msg; const apr_array_header_t *tag_tarr = NULL; const apr_table_entry_t *tag_telts = NULL; + for(j = 0; j < msr->removed_rules_msg->nelts; j++) { + re = ((rule_exception **)msr->removed_rules_msg->elts)[j]; + + if(rule->actionset->msg !=NULL) { + + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Checking removal of rule msg=\"%s\" against: %s", rule->actionset->msg, re->param); + } + + rc = msc_regexec(re->param_data, + rule->actionset->msg, strlen(rule->actionset->msg), + &my_error_msg); + if (rc >= 0) { + do_process = 0; + break; + } + } + } + for(j = 0; j < msr->removed_rules->nelts; j++) { range = ((const char**)msr->removed_rules->elts)[j]; @@ -1254,7 +1495,7 @@ static apr_status_t msre_ruleset_process_phase_(msre_ruleset *ruleset, modsec_re if((action != NULL) && (action->metadata != NULL ) && strcmp("tag", action->metadata->name) == 0) { for(j = 0; j < msr->removed_rules_tag->nelts; j++) { - range = ((const char**)msr->removed_rules_tag->elts)[j]; + re = ((rule_exception **)msr->removed_rules_tag->elts)[j]; if(action->param != NULL) { @@ -1266,13 +1507,17 @@ static apr_status_t msre_ruleset_process_phase_(msre_ruleset *ruleset, modsec_re expand_macros(msr, var, NULL, msr->mp); if (msr->txcfg->debuglog_level >= 9) { - msr_log(msr, 9, "Checking removal of rule tag=\"%s\" against: %s", var->value, range); + msr_log(msr, 9, "Checking removal of rule tag=\"%s\" against: %s", var->value, re->param); } - if (strncasecmp(var->value, range, strlen(range)) == 0) { + rc = msc_regexec(re->param_data, + var->value, strlen(var->value), + &my_error_msg); + if (rc >= 0) { do_process = 0; break; } + } } } @@ -1785,8 +2030,9 @@ char *msre_format_metadata(modsec_rec *msr, msre_actionset *actionset) { var->value_len = strlen(actionset->logdata); expand_macros(msr, var, NULL, msr->mp); - logdata = apr_psprintf(msr->mp, " [data \"%s\"]", + logdata = apr_psprintf(msr->mp, " [data \"%s", log_escape_hex(msr->mp, (unsigned char *)var->value, var->value_len)); + logdata = apr_pstrcat(msr->mp, logdata, "\"]", NULL); /* If it is > 512 bytes, then truncate at 512 with ellipsis. * NOTE: 512 actual data + 9 bytes of label = 521 @@ -1892,7 +2138,7 @@ msre_rule *msre_rule_create(msre_ruleset *ruleset, int type, msre_rule *rule; char *my_error_msg; const char *argsp; - int rc; + int rc, idx; if (error_msg == NULL) return NULL; *error_msg = NULL; @@ -2125,7 +2371,6 @@ static int execute_operator(msre_var *var, msre_rule *rule, modsec_rec *msr, apr_time_t time_before_op = 0; char *my_error_msg = NULL; const char *full_varname = NULL; - char *parm = NULL; int rc; /* determine the full var name if not already resolved @@ -2161,7 +2406,7 @@ static int execute_operator(msre_var *var, msre_rule *rule, modsec_rec *msr, #if defined(PERFORMANCE_MEASUREMENT) time_before_op = apr_time_now(); #else - if (msr->txcfg->debuglog_level >= 4) { + if (msr->txcfg->debuglog_level >= 4 || msr->txcfg->max_rule_time > 0) { time_before_op = apr_time_now(); } #endif @@ -2179,7 +2424,26 @@ static int execute_operator(msre_var *var, msre_rule *rule, modsec_rec *msr, apr_time_t t1 = apr_time_now(); msr_log(msr, 4, "Operator completed in %" APR_TIME_T_FMT " usec.", (t1 - time_before_op)); } - #endif + + if(msr->txcfg->max_rule_time > 0) { + apr_time_t t1 = apr_time_now(); + apr_time_t rule_time = 0; + const char *rt_time = NULL; + + if(rule->actionset->id != NULL) { + rt_time = apr_table_get(msr->perf_rules, rule->actionset->id); + if(rt_time == NULL) { + rt_time = apr_psprintf(msr->mp, "%" APR_TIME_T_FMT, (t1 - time_before_op)); + apr_table_setn(msr->perf_rules, rule->actionset->id, rt_time); + } else { + rule_time = (apr_time_t)atoi(rt_time); + rule_time += (t1 - time_before_op); + rt_time = apr_psprintf(msr->mp, "%" APR_TIME_T_FMT, rule_time); + apr_table_setn(msr->perf_rules, rule->actionset->id, rt_time); + } + } + } +#endif if (rc < 0) { msr_log(msr, 4, "Operator error: %s", my_error_msg); @@ -2211,36 +2475,20 @@ static int execute_operator(msre_var *var, msre_rule *rule, modsec_rec *msr, /* Save the last matched var data */ if(var != NULL && msr != NULL) { + msc_string *mvar = NULL; + msr->matched_var->name = apr_pstrdup(msr->mp, var->name); msr->matched_var->name_len = strlen(msr->matched_var->name); msr->matched_var->value = apr_pmemdup(msr->mp, var->value, var->value_len); msr->matched_var->value_len = var->value_len; - parm = strchr(msr->matched_var->name,':'); + mvar = apr_palloc(msr->mp, sizeof(msc_string)); + mvar->name = apr_pstrdup(msr->mp, var->name); + mvar->name_len = strlen(mvar->name); + mvar->value = apr_pmemdup(msr->mp, var->value, var->value_len); + mvar->value_len = var->value_len; - if(parm) { - msc_string *mvar = NULL; - - parm++; - - mvar = apr_palloc(msr->mp, sizeof(msc_string)); - mvar->name = apr_pstrdup(msr->mp, parm); - mvar->name_len = strlen(mvar->name); - mvar->value = apr_pmemdup(msr->mp, var->value, var->value_len); - mvar->value_len = var->value_len; - - apr_table_addn(msr->matched_vars, parm, (void *)mvar); - - } else { - - msc_string *mvar = apr_palloc(msr->mp, sizeof(msc_string)); - mvar->name = apr_pstrdup(msr->mp, var->name); - mvar->name_len = strlen(mvar->name); - mvar->value = apr_pmemdup(msr->mp, var->value, var->value_len); - mvar->value_len = var->value_len; - - apr_table_addn(msr->matched_vars, mvar->name, (void *)mvar); - } + apr_table_addn(msr->matched_vars, mvar->name, (void *)mvar); } diff --git a/apache2/re.h b/apache2/re.h index 7e66f219..d31e8e84 100644 --- a/apache2/re.h +++ b/apache2/re.h @@ -47,7 +47,16 @@ typedef struct msre_cache_rec msre_cache_rec; #endif /* Actions, variables, functions and operator functions */ -char DSOLOCAL *update_rule_target(cmd_parms *cmd, directory_config *dcfg, msre_ruleset *rset, const char *p1, const char *p2, const char *p3); +char DSOLOCAL *update_rule_target_ex(modsec_rec *msr, msre_ruleset *ruleset, msre_rule *rule, const char *p2, + const char *p3); + +int DSOLOCAL msre_ruleset_rule_matches_exception(msre_rule *rule, rule_exception *re); + +char DSOLOCAL *msre_ruleset_rule_update_target_matching_exception(modsec_rec *msr, msre_ruleset *ruleset, rule_exception *re, + const char *p2, const char *p3); + +char DSOLOCAL *msre_ruleset_phase_rule_update_target_matching_exception(modsec_rec *msr, msre_ruleset *ruleset, rule_exception *re, + apr_array_header_t *phase_arr, const char *p2, const char *p3); apr_status_t DSOLOCAL collection_original_setvar(modsec_rec *msr, const char *col_name, const msc_string *orig_var); @@ -295,7 +304,7 @@ struct msre_actionset { int intercept_action; const char *intercept_uri; int intercept_status; - int intercept_pause; + const char *intercept_pause; /* "block" needs parent action to reset it */ msre_action *parent_intercept_action_rec; diff --git a/apache2/re_actions.c b/apache2/re_actions.c index 7030746d..ac1288cb 100644 --- a/apache2/re_actions.c +++ b/apache2/re_actions.c @@ -370,6 +370,14 @@ apr_status_t collection_original_setvar(modsec_rec *msr, const char *col_name, c return 0; } +/* marker */ +static apr_status_t msre_action_marker_init(msre_engine *engine, msre_actionset *actionset, + msre_action *action) +{ + actionset->id = action->param; + return 1; +} + /* id */ static apr_status_t msre_action_id_init(msre_engine *engine, msre_actionset *actionset, @@ -379,10 +387,27 @@ static apr_status_t msre_action_id_init(msre_engine *engine, msre_actionset *act return 1; } +static char *msre_action_id_validate(msre_engine *engine, msre_action *action) { + int id; + + if(action != NULL && action->param != NULL) { + for(id=0;idparam);id++) { + if(!apr_isdigit(action->param[id])) + return apr_psprintf(engine->mp, "ModSecurity: Invalid value for action ID: %s", action->param); + } + id = atoi(action->param); + if ((id == LONG_MAX)||(id == LONG_MIN)||(id <= 0)) { + return apr_psprintf(engine->mp, "ModSecurity: Invalid value for action ID: %s", action->param); + } + } + + return NULL; +} + /* rev */ static apr_status_t msre_action_rev_init(msre_engine *engine, msre_actionset *actionset, - msre_action *action) + msre_action *action) { actionset->rev = action->param; return 1; @@ -534,7 +559,7 @@ static char *msre_action_pause_validate(msre_engine *engine, msre_action *action static apr_status_t msre_action_pause_init(msre_engine *engine, msre_actionset *actionset, msre_action *action) { - actionset->intercept_pause = atoi(action->param); + actionset->intercept_pause = action->param; return 1; } @@ -686,7 +711,15 @@ static char *msre_action_phase_validate(msre_engine *engine, msre_action *action static apr_status_t msre_action_phase_init(msre_engine *engine, msre_actionset *actionset, msre_action *action) { - actionset->phase = atoi(action->param); + if(strcasecmp(action->param,"request") == 0) + actionset->phase = 2; + else if(strcasecmp(action->param,"response") == 0) + actionset->phase = 4; + else if(strcasecmp(action->param,"logging") == 0) + actionset->phase = 5; + else + actionset->phase = atoi(action->param); + return 1; } @@ -734,7 +767,13 @@ static char *msre_action_ctl_validate(msre_engine *engine, msre_action *action) return NULL; } else if (strcasecmp(name, "ruleRemoveByTag") == 0) { - /* ENH nothing yet */ + if (!msc_pregcomp(engine->mp, value, 0, NULL, NULL)) + return apr_psprintf(engine->mp, "ModSecurity: Invalid regular expression \"%s\"", value); + return NULL; + } else + if (strcasecmp(name, "ruleRemoveByMsg") == 0) { + if (!msc_pregcomp(engine->mp, value, 0, NULL, NULL)) + return apr_psprintf(engine->mp, "ModSecurity: Invalid regular expression \"%s\"", value); return NULL; } else if (strcasecmp(name, "requestBodyAccess") == 0) { @@ -828,11 +867,44 @@ static char *msre_action_ctl_validate(msre_engine *engine, msre_action *action) if(parm == NULL && savedptr == NULL) return apr_psprintf(engine->mp, "ruleUpdateTargetById must has at least id;append_value"); - return NULL; - } - else { + return NULL; + } else + if (strcasecmp(name,"ruleUpdateTargetByTag") == 0) { + char *parm = NULL; + char *savedptr = NULL; + + parm = apr_strtok(value,";",&savedptr); + if(parm == NULL && savedptr == NULL) + return apr_psprintf(engine->mp, "ruleUpdateTargetByTag must has at least tag;append_value"); + if (!msc_pregcomp(engine->mp, parm, 0, NULL, NULL)) { + return apr_psprintf(engine->mp, "ModSecurity: Invalid regular expression \"%s\"", parm); + } + return NULL; + } else + if (strcasecmp(name,"ruleUpdateTargetByMsg") == 0) { + char *parm = NULL; + char *savedptr = NULL; + + parm = apr_strtok(value,";",&savedptr); + if(parm == NULL && savedptr == NULL) + return apr_psprintf(engine->mp, "ruleUpdateTargetByMsg must has at least msg;append_value"); + if (!msc_pregcomp(engine->mp, parm, 0, NULL, NULL)) { + return apr_psprintf(engine->mp, "ModSecurity: Invalid regular expression \"%s\"", parm); + } + return NULL; + } else + if (strcasecmp(name, "EncryptionEnforcement") == 0) { + if (strcasecmp(value, "on") == 0) return NULL; + if (strcasecmp(value, "off") == 0) return NULL; + return apr_psprintf(engine->mp, "Invalid setting for ctl name EncryptionEnforcement: %s", value); + } else + if (strcasecmp(name, "EncryptionEngine") == 0) { + if (strcasecmp(value, "on") == 0) return NULL; + if (strcasecmp(value, "off") == 0) return NULL; + return apr_psprintf(engine->mp, "Invalid setting for ctl name EncryptionEngine: %s", value); + } else { return apr_psprintf(engine->mp, "Invalid ctl name setting: %s", name); - } + } } static apr_status_t msre_action_ctl_init(msre_engine *engine, msre_actionset *actionset, @@ -879,17 +951,51 @@ static apr_status_t msre_action_ctl_execute(modsec_rec *msr, apr_pool_t *mptmp, return 1; } else + if (strcasecmp(name, "EncryptionEnforcement") == 0) { + if (strcasecmp(value, "on") == 0) { + msr->txcfg->encryption_enforcement = ENCRYPTION_ENABLED; + msr->usercfg->encryption_enforcement = ENCRYPTION_ENABLED; + } + if (strcasecmp(value, "off") == 0) { + msr->txcfg->encryption_enforcement = ENCRYPTION_DISABLED; + msr->usercfg->encryption_enforcement = ENCRYPTION_DISABLED; + } + if (msr->txcfg->debuglog_level >= 4) { + msr_log(msr, 4, "Ctl: Set EncryptionEnforcement to %s.", value); + } + } else + if (strcasecmp(name, "EncryptionEngine") == 0) { + if (strcasecmp(value, "on") == 0) { + msr->txcfg->encryption_is_enabled = ENCRYPTION_ENABLED; + msr->usercfg->encryption_is_enabled = ENCRYPTION_ENABLED; + } + if (strcasecmp(value, "off") == 0) { + msr->txcfg->encryption_is_enabled = ENCRYPTION_DISABLED; + msr->usercfg->encryption_is_enabled = ENCRYPTION_DISABLED; + } + if (msr->txcfg->debuglog_level >= 4) { + msr_log(msr, 4, "Ctl: Set EncryptionEngine to %s.", value); + } + } else if (strcasecmp(name, "ruleRemoveById") == 0) { *(const char **)apr_array_push(msr->removed_rules) = (const char *)apr_pstrdup(msr->mp, value); if (msr->txcfg->debuglog_level >= 4) { msr_log(msr, 4, "Ctl: Removed rule by id : %s.", value); } - return 1; } else if (strcasecmp(name, "ruleRemoveByTag") == 0) { - *(const char **)apr_array_push(msr->removed_rules_tag) = (const char *)apr_pstrdup(msr->mp, value); + rule_exception *re = apr_pcalloc(mptmp, sizeof(rule_exception)); + re->type = RULE_EXCEPTION_REMOVE_TAG; + re->param = (const char *)apr_pstrdup(msr->mp, value);; + re->param_data = msc_pregcomp(msr->mp, re->param, 0, NULL, NULL); + if (re->param_data == NULL) { + msr_log(msr, 1, "ModSecurity: Invalid regular expression \"%s\"", re->param); + return -1; + } + + *(rule_exception **)apr_array_push(msr->removed_rules_tag) = re; if (msr->txcfg->debuglog_level >= 4) { msr_log(msr, 4, "Ctl: Removed rule by tag : %s.", value); @@ -897,6 +1003,24 @@ static apr_status_t msre_action_ctl_execute(modsec_rec *msr, apr_pool_t *mptmp, return 1; } else + if (strcasecmp(name, "ruleRemoveByMsg") == 0) { + rule_exception *re = apr_pcalloc(mptmp, sizeof(rule_exception)); + re->type = RULE_EXCEPTION_REMOVE_MSG; + re->param = (const char *)apr_pstrdup(msr->mp, value);; + re->param_data = msc_pregcomp(msr->mp, re->param, 0, NULL, NULL); + if (re->param_data == NULL) { + msr_log(msr, 1, "ModSecurity: Invalid regular expression \"%s\"", re->param); + return -1; + } + + *(rule_exception **)apr_array_push(msr->removed_rules_msg) = re; + + if (msr->txcfg->debuglog_level >= 4) { + msr_log(msr, 4, "Ctl: Removed rule by msg : %s.", value); + } + + return 1; + } else if (strcasecmp(name, "requestBodyAccess") == 0) { int pv = parse_boolean(value); @@ -1047,6 +1171,7 @@ static apr_status_t msre_action_ctl_execute(modsec_rec *msr, apr_pool_t *mptmp, return 1; } else if (strcasecmp(name, "ruleUpdateTargetById") == 0) { + rule_exception *re = NULL; char *p1 = NULL, *p2 = NULL, *p3 = NULL; char *savedptr = NULL; @@ -1059,10 +1184,63 @@ static apr_status_t msre_action_ctl_execute(modsec_rec *msr, apr_pool_t *mptmp, if (msr->txcfg->debuglog_level >= 4) { msr_log(msr, 4, "Ctl: ruleUpdateTargetById id=%s append=%s replace=%s", p1, p2, p3); } + re = apr_pcalloc(mptmp, sizeof(rule_exception)); + re->type = RULE_EXCEPTION_REMOVE_ID; + re->param = p1; + msre_ruleset_rule_update_target_matching_exception(msr, rule->ruleset, re, p2, p3); + return 1; + } else + if (strcasecmp(name, "ruleUpdateTargetByTag") == 0) { + rule_exception *re = NULL; + char *p1 = NULL, *p2 = NULL, *p3 = NULL; + char *savedptr = NULL; - update_rule_target(NULL, NULL, rule->ruleset, p1, p2, p3); + p1 = apr_strtok(value,";",&savedptr); - return 1; + p2 = apr_strtok(NULL,";",&savedptr); + + p3 = apr_strtok(NULL,";",&savedptr); + + if (msr->txcfg->debuglog_level >= 4) { + msr_log(msr, 4, "Ctl: ruleUpdateTargetByTag tag=%s append=%s replace=%s", p1, p2, p3); + } + + re = apr_pcalloc(mptmp, sizeof(rule_exception)); + re->type = RULE_EXCEPTION_REMOVE_TAG; + re->param = p1; + re->param_data = msc_pregcomp(mptmp, p1, 0, NULL, NULL); + if (re->param_data == NULL) { + msr_log(msr, 1, "ModSecurity: Invalid regular expression \"%s\"", p1); + return -1; + } + msre_ruleset_rule_update_target_matching_exception(msr, rule->ruleset, re, p2, p3); + return 1; + } else + if (strcasecmp(name, "ruleUpdateTargetByMsg") == 0) { + rule_exception *re = NULL; + char *p1 = NULL, *p2 = NULL, *p3 = NULL; + char *savedptr = NULL; + + p1 = apr_strtok(value,";",&savedptr); + + p2 = apr_strtok(NULL,";",&savedptr); + + p3 = apr_strtok(NULL,";",&savedptr); + + if (msr->txcfg->debuglog_level >= 4) { + msr_log(msr, 4, "Ctl: ruleUpdateTargetByMsg tag=%s append=%s replace=%s", p1, p2, p3); + } + + re = apr_pcalloc(mptmp, sizeof(rule_exception)); + re->type = RULE_EXCEPTION_REMOVE_MSG; + re->param = p1; + re->param_data = msc_pregcomp(mptmp, p1, 0, NULL, NULL); + if (re->param_data == NULL) { + msr_log(msr, 1, "ModSecurity: Invalid regular expression \"%s\"", p1); + return -1; + } + msre_ruleset_rule_update_target_matching_exception(msr, rule->ruleset, re, p2, p3); + return 1; } else { /* Should never happen, but log if it does. */ @@ -1855,7 +2033,7 @@ static apr_status_t msre_action_initcol_execute(modsec_rec *msr, apr_pool_t *mpt /* IMP1 We have a function for this now, parse_name_eq_value? */ s = strstr(data, "="); if (s == NULL) return 0; - col_name = strtolower_inplace(data); + col_name = strtolower_inplace((unsigned char *)data); col_key = s + 1; *s = '\0'; @@ -1919,6 +2097,29 @@ static apr_status_t msre_action_setuid_execute(modsec_rec *msr, apr_pool_t *mptm return init_collection(msr, real_col_name, "USER", col_key, col_key_len); } +/* setrsc */ +static apr_status_t msre_action_setrsc_execute(modsec_rec *msr, apr_pool_t *mptmp, + msre_rule *rule, msre_action *action) +{ + msc_string *var = NULL; + char *real_col_name = NULL, *col_key = NULL; + unsigned int col_key_len; + + /* Construct user ID. */ + var = apr_pcalloc(mptmp, sizeof(msc_string)); + var->value = (char *)action->param; + var->value_len = strlen(var->value); + expand_macros(msr, var, rule, mptmp); + + /* Construct collection name. */ + col_key = var->value; + col_key_len = var->value_len; + real_col_name = apr_psprintf(mptmp, "%s_RESOURCE", msr->txcfg->webappid); + + /* Initialise collection. */ + return init_collection(msr, real_col_name, "RESOURCE", col_key, col_key_len); +} + /* exec */ static char *msre_action_exec_validate(msre_engine *engine, msre_action *action) { #if defined(WITH_LUA) @@ -2027,11 +2228,24 @@ void msre_engine_register_default_actions(msre_engine *engine) { NO_PLUS_MINUS, ACTION_CARDINALITY_ONE, ACTION_CGROUP_NONE, - NULL, + msre_action_id_validate, msre_action_id_init, NULL ); + /* marker */ + msre_engine_action_register(engine, + "marker", + ACTION_METADATA, + 1, 1, + NO_PLUS_MINUS, + ACTION_CARDINALITY_ONE, + ACTION_CGROUP_NONE, + NULL, + msre_action_marker_init, + NULL + ); + /* rev */ msre_engine_action_register(engine, "rev", @@ -2566,6 +2780,19 @@ void msre_engine_register_default_actions(msre_engine *engine) { msre_action_setsid_execute ); + /* setuid */ + msre_engine_action_register(engine, + "setrsc", + ACTION_NON_DISRUPTIVE, + 1, 1, + NO_PLUS_MINUS, + ACTION_CARDINALITY_ONE, + ACTION_CGROUP_NONE, + NULL, + NULL, + msre_action_setrsc_execute + ); + /* setuid */ msre_engine_action_register(engine, "setuid", diff --git a/apache2/re_operators.c b/apache2/re_operators.c index d055906b..dc4e4fd1 100644 --- a/apache2/re_operators.c +++ b/apache2/re_operators.c @@ -20,6 +20,8 @@ #include "apr_strmatch.h" #include "acmp.h" #include "msc_util.h" +#include "msc_crypt.h" + #if !defined(WIN32) || !defined(WINNT) #include #endif @@ -75,11 +77,11 @@ static int msre_op_nomatch_execute(modsec_rec *msr, msre_rule *rule, /* ipmatch */ -/* +/** * \brief Init function to ipmatch operator * -* \param rule Pointer to the rule -* \param error_msg Pointer to error msg +* \param rule ModSecurity rule struct +* \param error_msg Error message * * \retval 1 On Success * \retval 0 On Fail @@ -133,7 +135,7 @@ static int msre_op_ipmatch_param_init(msre_rule *rule, char **error_msg) { return 1; } -/* +/** * \brief Execution function to ipmatch operator * * \param msr Pointer internal modsec request structure @@ -201,7 +203,7 @@ static char *param_remove_escape(msre_rule *rule, char *str, int len) { return ret; } -/* +/** * \brief Init function to rsub operator * * \param rule Pointer to the rule @@ -223,10 +225,12 @@ static int msre_op_rsub_param_init(msre_rule *rule, char **error_msg) { char *replace = NULL; char *e_pattern = NULL; char *e_replace = NULL; + char *parsed_replace = NULL; char *flags = NULL; char *data = NULL; char delim; int ignore_case = 0; + unsigned short int op_len = 0; if (error_msg == NULL) return -1; *error_msg = NULL; @@ -235,10 +239,11 @@ static int msre_op_rsub_param_init(msre_rule *rule, char **error_msg) { if (apr_tolower(*line) != 's') { *error_msg = apr_psprintf(rule->ruleset->mp, "Error rsub operator format, must be s/ pattern"); - return 0; + return -1; } data = apr_pstrdup(rule->ruleset->mp, line); + delim = *++data; if (delim) reg_pattern = ++data; @@ -292,7 +297,15 @@ static int msre_op_rsub_param_init(msre_rule *rule, char **error_msg) { return -1; } - e_replace = param_remove_escape(rule, replace, strlen(replace)); + op_len = strlen(replace); + parsed_replace = apr_pstrdup(rule->ruleset->mp, parse_pm_content(replace, op_len, rule, error_msg)); + + if(!parsed_replace) { + *error_msg = apr_psprintf(rule->ruleset->mp, "Error rsub operator parsing input data"); + return -1; + } + + e_replace = param_remove_escape(rule, parsed_replace, strlen(parsed_replace)); rule->sub_str = apr_pstrmemdup(rule->ruleset->mp, e_replace, strlen(e_replace)); if (flags) { @@ -329,7 +342,7 @@ static int msre_op_rsub_param_init(msre_rule *rule, char **error_msg) { return 1; /* OK */ } -/* +/** * \brief Execution function to rsub operator * * \param msr Pointer internal modsec request structure @@ -345,11 +358,11 @@ static int msre_op_rsub_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, msc_string *str = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); msc_string *re_pattern = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); char *offset = NULL; - int sub = 0, so = 0, p_len = 0; - char *replace = NULL; char *data = NULL, *pattern = NULL; - unsigned int size = var->value_len; - int output_body = 0, input_body = 0, count = 0; + char *data_out = NULL; + unsigned int size = 0; + unsigned int maxsize=0; + int output_body = 0, input_body = 0, sl; #if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER > 0 ap_regmatch_t pmatch[AP_MAX_REG_MATCH]; #else @@ -390,7 +403,6 @@ static int msre_op_rsub_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, #else rule->sub_regex = ap_pregcomp(msr->mp, re_pattern->value, REG_EXTENDED); #endif - } } else { rule->sub_regex = NULL; @@ -400,7 +412,7 @@ static int msre_op_rsub_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, if(rule->sub_regex == NULL) { *error_msg = "Internal Error: regex data is null."; - return 0; + return -1; } str->value = apr_pstrndup(msr->mp, rule->sub_str, strlen(rule->sub_str)); @@ -409,81 +421,122 @@ static int msre_op_rsub_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, if(strstr(rule->sub_str,"%{") != NULL) expand_macros(msr, str, rule, msr->mp); - replace = apr_pstrndup(msr->mp, str->value, str->value_len); - data = apr_pcalloc(msr->mp, var->value_len+(AP_MAX_REG_MATCH*strlen(replace))+1); + maxsize=var->value_len+(AP_MAX_REG_MATCH*1024)+1; - if(replace == NULL || data == NULL) { +nextround: + + data = apr_pcalloc(msr->mp, maxsize+1); + + if(data == NULL) { *error_msg = "Internal Error: cannot allocate memory"; return -1; } - memcpy(data,var->value,var->value_len); - - size += (AP_MAX_REG_MATCH*strlen(replace)+2); - - if (ap_regexec(rule->sub_regex, data ,AP_MAX_REG_MATCH, pmatch, 0)) return 0; - - for (offset = replace; *offset; offset++) - if (*offset == '\\' && *(offset + 1) > '0' && *(offset + 1) <= '9') { - so = pmatch [*(offset + 1) - 48].rm_so; - p_len = pmatch [*(offset + 1) - 48].rm_eo - so; - if (so < 0 || strlen (replace) + p_len - 1 > size) return 0; - memmove (offset + p_len, offset + 2, strlen (offset) - 1); - memmove (offset, data + so, p_len); - offset = offset + p_len - 2; + data_out=data; + size=0; + for (offset = (char*)var->value; !ap_regexec(rule->sub_regex, offset, AP_MAX_REG_MATCH, pmatch, 0); ) { + //Copy of data before the regex match + int i; + int s = pmatch [0].rm_so; + int p_len=pmatch [0].rm_eo - pmatch [0].rm_so; + if (size+s>maxsize) { + maxsize*=2; + goto nextround; } + memcpy(data_out,offset,s); + data_out+=s; + size+=s; - sub = -1; + //Copy of regex match with replacing data \1..\9 + for(i=0;ivalue_len;) { + char *x = str->value+i; + if (*x == '\\' && *(x + 1) > '0' && *(x + 1) <= '9') { + int capture=*(x + 1) - 48; + int capture_len=pmatch[capture].rm_eo-pmatch[capture].rm_so; - for (offset = data; !ap_regexec(rule->sub_regex, offset, 1, pmatch, 0); ) { - p_len = pmatch [0].rm_eo - pmatch [0].rm_so; - count++; - offset += pmatch [0].rm_so; - if (var->value_len - p_len + strlen(replace) + 1 > size) return 0; - memmove (offset + strlen (replace), offset + p_len, strlen (offset) - p_len + 1); - memmove (offset, replace, strlen (replace)); - offset += strlen (replace); - if (sub >= 0) break; + if (size+capture_len>maxsize) + { + maxsize*=2; + goto nextround; + } + memcpy(data_out,offset+pmatch[capture].rm_so,capture_len); + data_out+= capture_len; + size+=capture_len; + i+=2; + } else { + + if (size+1>maxsize) { + maxsize*=2; + goto nextround; + } + *data_out=*(str->value+i); + data_out++; + size++; + i++; + } + } + offset+=s; + offset+=p_len; } - size -= (((AP_MAX_REG_MATCH - count)*(strlen(replace))) + p_len+2); + //Copy of data after the last regex match + sl = strlen(offset); + if (size+sl>maxsize) { + maxsize*=2; + goto nextround; + } + memcpy(data_out,offset,sl); + data_out+=sl; + size+=sl; + *data_out=0; if(msr->stream_output_data != NULL && output_body == 1) { - char *stream_output_data = NULL; + memset(msr->stream_output_data, 0x0, msr->stream_output_length); + free(msr->stream_output_data); + msr->stream_output_data = NULL; + msr->stream_output_length = 0; - stream_output_data = (char *)realloc(msr->stream_output_data, size+1); - msr->stream_output_length = size; + msr->stream_output_data = (char *)malloc(size+1); - if(stream_output_data == NULL) { + if(msr->stream_output_data == NULL) { return -1; } - var->value_len = size; + msr->stream_output_length = size; + memset(msr->stream_output_data, 0x0, size+1); + msr->of_stream_changed = 1; - msr->stream_output_data = (char *)stream_output_data; - if(msr->stream_output_data != NULL) - apr_cpystrn(msr->stream_output_data, data, size); + strncpy(msr->stream_output_data, data, size); + msr->stream_output_data[size] = '\0'; + var->value_len = size; + var->value = msr->stream_output_data; } if(msr->stream_input_data != NULL && input_body == 1) { - char *stream_input_data = NULL; + memset(msr->stream_input_data, 0x0, msr->stream_input_length); + free(msr->stream_input_data); + msr->stream_input_data = NULL; + msr->stream_input_length = 0; - stream_input_data = (char *)realloc(msr->stream_input_data, size+1); - msr->stream_input_length = size; + msr->stream_input_data = (char *)malloc(size+1); - if(stream_input_data == NULL) { + if(msr->stream_input_data == NULL) { return -1; } - var->value_len = size; + msr->stream_input_length = size; + memset(msr->stream_input_data, 0x0, size+1); + msr->if_stream_changed = 1; - msr->stream_input_data = (char *)stream_input_data; - if(msr->stream_input_data != NULL) - apr_cpystrn(msr->stream_input_data, data, size); + strncpy(msr->stream_input_data, data, size); + msr->stream_input_data[size] = '\0'; + + var->value_len = size; + var->value = msr->stream_input_data; } if (! *error_msg) { @@ -494,6 +547,245 @@ static int msre_op_rsub_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, } #endif /* MSC_TEST */ +/** + * \brief Init function to validateEncryption + * + * \param rule ModSecurity rule struct + * \param error_msg Error message + * + * \retval 1 On success + * \retval 0 On fail + */ +static int msre_op_validateEncryption_param_init(msre_rule *rule, char **error_msg) { + const char *errptr = NULL; + int erroffset; + msc_regex_t *regex; + const char *pattern = rule->op_param; + int rc, jit; + + if (error_msg == NULL) return -1; + *error_msg = NULL; + + /* Compile pattern */ + if(strstr(pattern,"%{") == NULL) { + regex = msc_pregcomp_ex(rule->ruleset->mp, pattern, PCRE_DOTALL | PCRE_DOLLAR_ENDONLY, &errptr, &erroffset, msc_pcre_match_limit, msc_pcre_match_limit_recursion); + if (regex == NULL) { + *error_msg = apr_psprintf(rule->ruleset->mp, "Error compiling pattern (offset %d): %s", + erroffset, errptr); + return 0; + } + + #ifdef WITH_PCRE_STUDY + #ifdef WITH_PCRE_JIT + rc = msc_fullinfo(regex, PCRE_INFO_JIT, &jit); + if ((rc != 0) || (jit != 1)) { + *error_msg = apr_psprintf(rule->ruleset->mp, + "Rule %pp [id \"%s\"][file \"%s\"][line \"%d\"] - " + "Execution error - " + "Does not support JIT (%d)", + rule,((rule->actionset != NULL)&&(rule->actionset->id != NULL)) ? rule->actionset->id : "-", + rule->filename != NULL ? rule->filename : "-", + rule->line_num,rc); + } + #endif + #endif + + rule->op_param_data = regex; + } else { + rule->re_precomp = 1; + rule->re_str = apr_pstrndup(rule->ruleset->mp, pattern, strlen(pattern)); + rule->op_param_data = NULL; + } + + return 1; /* OK */ +} + +/** + * \brief Execute function to validateEncryption + * + * \param msr ModSecurity transaction resource + * \param rule ModSecurity rule struct + * \param var ModSecurity variable struct + * \param error_msg Error message + * + * \retval 1 On success + * \retval 0 On fail + */ +static int msre_op_validateEncryption_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) { + msc_regex_t *regex = (msc_regex_t *)rule->op_param_data; + msc_string *re_pattern = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); + const char *target; + const char *errptr = NULL; + int erroffset; + unsigned int target_length; + char *my_error_msg = NULL; + int ovector[33]; + int rc, jit; + char *qspos = NULL; + const char *parm = NULL, *pattern = NULL; + msc_parm *mparm = NULL; + + if (error_msg == NULL) return -1; + *error_msg = NULL; + + if (msr->txcfg->encryption_enforcement == ENCRYPTION_DISABLED || msr->txcfg->encryption_is_enabled == ENCRYPTION_DISABLED) + return 0; + + if (regex == NULL) { + if(rule->re_precomp == 0) { + *error_msg = "Internal Error: regex data is null."; + return -1; + } else { + + if(re_pattern == NULL) { + *error_msg = "Internal Error: regex variable data is null."; + return -1; + } + + re_pattern->value = apr_pstrndup(msr->mp, rule->re_str, strlen(rule->re_str)); + re_pattern->value_len = strlen(re_pattern->value); + + expand_macros(msr, re_pattern, rule, msr->mp); + + pattern = log_escape_re(msr->mp, re_pattern->value); + if (msr->txcfg->debuglog_level >= 6) { + msr_log(msr, 6, "Escaping pattern [%s]",pattern); + } + + regex = msc_pregcomp_ex(rule->ruleset->mp, pattern, PCRE_DOTALL | PCRE_DOLLAR_ENDONLY, &errptr, + &erroffset, msc_pcre_match_limit, msc_pcre_match_limit_recursion); + if (regex == NULL) { + *error_msg = apr_psprintf(rule->ruleset->mp, "Error compiling pattern (offset %d): %s", + erroffset, errptr); + return 0; + } + + #ifdef WITH_PCRE_STUDY + #ifdef WITH_PCRE_JIT + if (msr->txcfg->debuglog_level >= 4) { + rc = msc_fullinfo(regex, PCRE_INFO_JIT, &jit); + if ((rc != 0) || (jit != 1)) { + *error_msg = apr_psprintf(rule->ruleset->mp, + "Rule %pp [id \"%s\"][file \"%s\"][line \"%d\"] - " + "Execution error - " + "Does not support JIT (%d)", + rule,((rule->actionset != NULL)&&(rule->actionset->id != NULL)) ? rule->actionset->id : "-", + rule->filename != NULL ? rule->filename : "-", + rule->line_num,rc); + msr_log(msr, 4, "%s.", *error_msg); + } + } + #endif + #endif + } + } + + /* If the given target is null run against an empty + * string. This is a behaviour consistent with previous + * releases. + */ + if (var->value == NULL) { + target = ""; + target_length = 0; + } else { + target = var->value; + target_length = var->value_len; + } + + /* We always use capture so that ovector can be used as working space + * and no memory has to be allocated for any backreferences. + */ + rc = msc_regexec_capture(regex, target, target_length, ovector, 30, &my_error_msg); + if ((rc == PCRE_ERROR_MATCHLIMIT) || (rc == PCRE_ERROR_RECURSIONLIMIT)) { + msc_string *s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); + + if (s == NULL) return -1; + s->name = apr_pstrdup(msr->mp, "MSC_PCRE_LIMITS_EXCEEDED"); + s->name_len = strlen(s->name); + s->value = apr_pstrdup(msr->mp, "1"); + s->value_len = 1; + if ((s->name == NULL)||(s->value == NULL)) return -1; + apr_table_setn(msr->tx_vars, s->name, (void *)s); + + *error_msg = apr_psprintf(msr->mp, + "Rule %pp [id \"%s\"][file \"%s\"][line \"%d\"] - " + "Execution error - " + "PCRE limits exceeded (%d): %s", + 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); + + return 0; /* No match. */ + } + else if (rc < -1) { + *error_msg = apr_psprintf(msr->mp, "Regex execution failed (%d): %s", + rc, my_error_msg); + return -1; + } + + if (rc != PCRE_ERROR_NOMATCH) { /* Match. */ + /* We no longer escape the pattern here as it is done when logging */ + char *pattern = apr_pstrdup(msr->mp, log_escape(msr->mp, regex->pattern ? regex->pattern : "")); + char *hmac = NULL, *valid = NULL; + char *hash_link = NULL, *nlink = NULL; + + if (strlen(pattern) > 252) { + *error_msg = apr_psprintf(msr->mp, "Request URI matched \"%.252s ...\" at %s.", + pattern, var->name); + } else { + *error_msg = apr_psprintf(msr->mp, "Request URI matched \"%s\" at %s.", + pattern, var->name); + } + + valid = strstr(target, msr->txcfg->crypto_param_name); + + if(valid == NULL) { + + if (msr->txcfg->debuglog_level >= 9) + msr_log(msr, 9, "Request URI without encryption parameter [%s]", target); + + if (strlen(pattern) > 252) { + *error_msg = apr_psprintf(msr->mp, "Request URI matched \"%.252s ...\" at %s. No Encryption parameter", + pattern, var->name); + } else { + *error_msg = apr_psprintf(msr->mp, "Request URI matched \"%s\" at %s. No Encryption parameter", + pattern, var->name); + } + return 1; + } else { + + if(strlen(valid) < strlen(msr->txcfg->crypto_param_name)+1) + return 1; + + hmac = valid+strlen(msr->txcfg->crypto_param_name)+1; + + nlink = apr_pstrmemdup(msr->mp, target, strlen(target) - strlen(valid) - 1); + + msr_log(msr, 9, "Validating URI %s size %d",nlink,strlen(nlink)); + + hash_link = do_hash_link(msr, (char *)nlink, HASH_ONLY); + + if(strcmp(hmac, hash_link) != 0) { + + if (strlen(pattern) > 252) { + *error_msg = apr_psprintf(msr->mp, "Request URI matched \"%.252s ...\" at %s. Encryption parameter hash value = [%s] Requested URI hash value = [%s]", + pattern, var->name, hmac, hash_link); + } else { + *error_msg = apr_psprintf(msr->mp, "Request URI matched \"%s\" at %s. Encryption parameter hash value = [%s] Requested URI hash value = [%s]", + pattern, var->name, hmac, hash_link); + } + return 1; + } + } + + return 0; + } + + return 0; +} + /* rx */ static int msre_op_rx_param_init(msre_rule *rule, char **error_msg) { @@ -625,7 +917,12 @@ static int msre_op_rx_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, c /* Are we supposed to capture subexpressions? */ capture = apr_table_get(rule->actionset->actions, "capture") ? 1 : 0; matched_bytes = apr_table_get(rule->actionset->actions, "sanitizeMatchedBytes") ? 1 : 0; + if(!matched_bytes) + matched_bytes = apr_table_get(rule->actionset->actions, "sanitiseMatchedBytes") ? 1 : 0; + matched = apr_table_get(rule->actionset->actions, "sanitizeMatched") ? 1 : 0; + if(!matched) + matched = apr_table_get(rule->actionset->actions, "sanitiseMatched") ? 1 : 0; /* Show when the regex captures but "capture" is not set */ if (msr->txcfg->debuglog_level >= 6) { @@ -753,117 +1050,6 @@ static int msre_op_rx_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, c /* pm */ -static char *parse_pm_content(const char *op_parm, unsigned short int op_len, msre_rule *rule, char **error_msg) { - char *parm = NULL; - char *content = NULL; - unsigned short int offset = 0; - char converted = 0; - int i, x; - unsigned char bin = 0, esc = 0, bin_offset = 0; - unsigned char bin_parm[3], c = 0; - char *processed = NULL; - - content = apr_pstrdup(rule->ruleset->mp, op_parm); - - if (content == NULL) { - *error_msg = apr_psprintf(rule->ruleset->mp, "Error allocating memory for pattern matching content."); - return NULL; - } - - while (offset < op_len && apr_isspace(content[offset])) { - offset++; - }; - - op_len = strlen(content); - - if (content[offset] == '\"' && content[op_len-1] == '\"') { - parm = apr_pstrdup(rule->ruleset->mp, content + offset + 1); - if (parm == NULL) { - *error_msg = apr_psprintf(rule->ruleset->mp, "Error allocating memory for pattern matching content."); - return NULL; - } - parm[op_len - offset - 2] = '\0'; - } else { - parm = apr_pstrdup(rule->ruleset->mp, content + offset); - if (parm == NULL) { - *error_msg = apr_psprintf(rule->ruleset->mp, "Error allocating memory for pattern matching content."); - return NULL; - } - } - - op_len = strlen(parm); - - if (op_len == 0) { - *error_msg = apr_psprintf(rule->ruleset->mp, "Content length is 0."); - return NULL; - } - - - for (i = 0, x = 0; i < op_len; i++) { - if (parm[i] == '|') { - if (bin) { - bin = 0; - } else { - bin = 1; - } - } else if(!esc && parm[i] == '\\') { - esc = 1; - } else { - if (bin) { - if (apr_isdigit(parm[i]) || - parm[i] == 'A' || parm[i] == 'a' || - parm[i] == 'B' || parm[i] == 'b' || - parm[i] == 'C' || parm[i] == 'c' || - parm[i] == 'D' || parm[i] == 'd' || - parm[i] == 'E' || parm[i] == 'e' || - parm[i] == 'F' || parm[i] == 'f') - { - bin_parm[bin_offset] = (char)parm[i]; - bin_offset++; - if (bin_offset == 2) { - c = strtol((char *)bin_parm, (char **) NULL, 16) & 0xFF; - bin_offset = 0; - parm[x] = c; - x++; - converted = 1; - } - } else if (parm[i] == ' ') { - } - } else if (esc) { - if (parm[i] == ':' || - parm[i] == ';' || - parm[i] == '\\' || - parm[i] == '\"') - { - parm[x] = parm[i]; - x++; - } else { - *error_msg = apr_psprintf(rule->ruleset->mp, "Unsupported escape sequence."); - return NULL; - } - esc = 0; - converted = 1; - } else { - parm[x] = parm[i]; - x++; - } - } - } - - if (converted) { - op_len = x; - } - - processed = apr_pstrmemdup(rule->ruleset->mp, parm, op_len); - - if (processed == NULL) { - *error_msg = apr_psprintf(rule->ruleset->mp, "Error allocating memory for pattern matching content."); - return NULL; - } - - return processed; -} - static int msre_op_pm_param_init(msre_rule *rule, char **error_msg) { ACMP *p; const char *phrase; @@ -929,9 +1115,9 @@ static int msre_op_pmFromFile_param_init(msre_rule *rule, char **error_msg) { /* Get the path of the rule filename to use as a base */ rulefile_path = apr_pstrndup(rule->ruleset->mp, rule->filename, strlen(rule->filename) - strlen(apr_filepath_name_get(rule->filename))); -#ifdef DEBUG_CONF + #ifdef DEBUG_CONF fprintf(stderr, "Rulefile path: \"%s\"\n", rulefile_path); -#endif + #endif /* Loop through filenames */ /* ENH: Need to allow quoted filenames w/space */ @@ -962,9 +1148,9 @@ static int msre_op_pmFromFile_param_init(msre_rule *rule, char **error_msg) { return 0; } -#ifdef DEBUG_CONF + #ifdef DEBUG_CONF fprintf(stderr, "Loading phrase file: \"%s\"\n", fn); -#endif + #endif /* Read one pattern per line skipping empty/commented */ for(;;) { @@ -1071,7 +1257,7 @@ static int msre_op_pm_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, c /* gsbLookup */ -/* +/** * \brief Reduce /./ to / * * \param pool Pointer to the memory pool @@ -1109,10 +1295,10 @@ static const char *gsb_replace_tpath(apr_pool_t *pool, const char *domain, int l return url; } -/* - * \brief Reduce doble dot to single dot +/** + * \brief Reduce double dot to single dot * - * \param msr Pointer to the modsec resource + * \param msr ModSecurity transation resource * \param domain Input data * * \retval domain On Failure @@ -1174,10 +1360,10 @@ static const char *gsb_reduce_char(apr_pool_t *pool, const char *domain) { } -/* +/** * \brief Verify function to gsbLookup operator * - * \param msr Pointer to the modsec resource + * \param msr ModSecurity transaction resource * \param match Pointer to input data * \param match_length Input size * @@ -1213,11 +1399,11 @@ static int verify_gsb(gsb_db *gsb, modsec_rec *msr, const char *match, unsigned return 0; } -/* +/** * \brief Init function to gsbLookup operator * - * \param rule Pointer to the rule - * \param error_msg Pointer to error msg + * \param rule ModSecurity rule struct + * \param error_msg Error message * * \retval 1 On Success * \retval 0 On Fail @@ -1244,13 +1430,13 @@ static int msre_op_gsbLookup_param_init(msre_rule *rule, char **error_msg) { return 1; /* OK */ } -/* +/** * \brief Execution function to gsbLookup operator * - * \param msr Pointer internal modsec request structure - * \param rule Pointer to the rule - * \param var Pointer to variable structure - * \param error_msg Pointer to error msg + * \param msr ModSecurity transaction resource + * \param rule ModSecurity rule struct + * \param var ModSecurity variable struct + * \param error_msg Error message * * \retval -1 On Failure * \retval 1 On Match @@ -2283,9 +2469,14 @@ static int msre_op_verifyCC_execute(modsec_rec *msr, msre_rule *rule, msre_var * unsigned int target_length; char *my_error_msg = NULL; int ovector[33]; - int rc; + int rc, jit; int is_cc = 0; int offset; + int matched_bytes = 0; + char *qspos = NULL; + const char *parm = NULL; + msc_parm *mparm = NULL; + if (error_msg == NULL) return -1; *error_msg = NULL; @@ -2297,6 +2488,25 @@ static int msre_op_verifyCC_execute(modsec_rec *msr, msre_rule *rule, msre_var * memset(ovector, 0, sizeof(ovector)); + #ifdef WITH_PCRE_STUDY + #ifdef WITH_PCRE_JIT + if (msr->txcfg->debuglog_level >= 4) { + rc = msc_fullinfo(regex, PCRE_INFO_JIT, &jit); + if ((rc != 0) || (jit != 1)) { + *error_msg = apr_psprintf(rule->ruleset->mp, + "Rule %pp [id \"%s\"][file \"%s\"][line \"%d\"] - " + "Execution error - " + "Does not support JIT (%d)", + rule,((rule->actionset != NULL)&&(rule->actionset->id != NULL)) ? rule->actionset->id : "-", + rule->filename != NULL ? rule->filename : "-", + rule->line_num,rc); + msr_log(msr, 4, "%s.", *error_msg); + } + } + #endif + #endif + + /* If the given target is null run against an empty * string. This is a behaviour consistent with previous * releases. @@ -2352,6 +2562,11 @@ static int msre_op_verifyCC_execute(modsec_rec *msr, msre_rule *rule, msre_var * * and we are done. */ + matched_bytes = apr_table_get(rule->actionset->actions, "sanitizeMatchedBytes") ? 1 : 0; + if(!matched_bytes) + matched_bytes = apr_table_get(rule->actionset->actions, "sanitiseMatchedBytes") ? 1 : 0; + + if (apr_table_get(rule->actionset->actions, "capture")) { for(; i < rc; i++) { msc_string *s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); @@ -2368,6 +2583,30 @@ static int msre_op_verifyCC_execute(modsec_rec *msr, msre_rule *rule, msre_var * msr_log(msr, 9, "Added regex subexpression to TX.%d: %s", i, log_escape_nq_ex(msr->mp, s->value, s->value_len)); } + + if((matched_bytes == 1) && (var != NULL) && (var->name != NULL)) { + qspos = apr_psprintf(msr->mp, "%s", var->name); + parm = strstr(qspos, ":"); + if (parm != NULL) { + parm++; + mparm = apr_palloc(msr->mp, sizeof(msc_parm)); + if (mparm == NULL) + continue; + + mparm->value = apr_pstrmemdup(msr->mp,s->value,s->value_len); + mparm->pad_1 = rule->actionset->arg_min; + mparm->pad_2 = rule->actionset->arg_max; + apr_table_addn(msr->pattern_to_sanitize, parm, (void *)mparm); + } else { + mparm = apr_palloc(msr->mp, sizeof(msc_parm)); + if (mparm == NULL) + continue; + + mparm->value = apr_pstrmemdup(msr->mp,s->value,s->value_len); + apr_table_addn(msr->pattern_to_sanitize, qspos, (void *)mparm); + } + } + } } @@ -2396,7 +2635,7 @@ static int msre_op_verifyCC_execute(modsec_rec *msr, msre_rule *rule, msre_var * return 0; } -/* +/** * \brief Check for a valid CPF * * \param cpfnumber Pointer to cpf @@ -2484,11 +2723,11 @@ static int cpf_verify(const char *cpfnumber, int len) { return 0; } -/* +/** * \brief Init function to CPF operator * - * \param rule Pointer to the rule - * \param error_msg Pointer to error msg + * \param rule ModSecurity rule struct + * \param error_msg Error message * * \retval 0 On Failure * \retval 1 On Success @@ -2514,13 +2753,13 @@ static int msre_op_verifyCPF_init(msre_rule *rule, char **error_msg) { return 1; /* OK */ } -/* +/** * \brief Execution function to CPF operator * - * \param msr Pointer internal modsec request structure - * \param rule Pointer to the rule - * \param var Pointer to variable structure - * \param error_msg Pointer to error msg + * \param msr ModSecurity transaction resource + * \param rule ModSecurity rule struct + * \param var ModSecurity variable struct + * \param error_msg Error message * * \retval -1 On Failure * \retval 1 On Match @@ -2532,9 +2771,13 @@ static int msre_op_verifyCPF_execute(modsec_rec *msr, msre_rule *rule, msre_var unsigned int target_length; char *my_error_msg = NULL; int ovector[33]; - int rc; + int rc, jit; int is_cpf = 0; int offset; + int matched_bytes = 0; + char *qspos = NULL; + const char *parm = NULL; + msc_parm *mparm = NULL; if (error_msg == NULL) return -1; *error_msg = NULL; @@ -2546,6 +2789,24 @@ static int msre_op_verifyCPF_execute(modsec_rec *msr, msre_rule *rule, msre_var memset(ovector, 0, sizeof(ovector)); + #ifdef WITH_PCRE_STUDY + #ifdef WITH_PCRE_JIT + if (msr->txcfg->debuglog_level >= 4) { + rc = msc_fullinfo(regex, PCRE_INFO_JIT, &jit); + if ((rc != 0) || (jit != 1)) { + *error_msg = apr_psprintf(rule->ruleset->mp, + "Rule %pp [id \"%s\"][file \"%s\"][line \"%d\"] - " + "Execution error - " + "Does not support JIT (%d)", + rule,((rule->actionset != NULL)&&(rule->actionset->id != NULL)) ? rule->actionset->id : "-", + rule->filename != NULL ? rule->filename : "-", + rule->line_num,rc); + msr_log(msr, 4, "%s.", *error_msg); + } + } + #endif + #endif + /* If the given target is null run against an empty * string. This is a behaviour consistent with previous * releases. @@ -2601,6 +2862,10 @@ static int msre_op_verifyCPF_execute(modsec_rec *msr, msre_rule *rule, msre_var * and we are done. */ + matched_bytes = apr_table_get(rule->actionset->actions, "sanitizeMatchedBytes") ? 1 : 0; + if(!matched_bytes) + matched_bytes = apr_table_get(rule->actionset->actions, "sanitiseMatchedBytes") ? 1 : 0; + if (apr_table_get(rule->actionset->actions, "capture")) { for(; i < rc; i++) { msc_string *s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); @@ -2617,6 +2882,30 @@ static int msre_op_verifyCPF_execute(modsec_rec *msr, msre_rule *rule, msre_var msr_log(msr, 9, "Added regex subexpression to TX.%d: %s", i, log_escape_nq_ex(msr->mp, s->value, s->value_len)); } + + if((matched_bytes == 1) && (var != NULL) && (var->name != NULL)) { + qspos = apr_psprintf(msr->mp, "%s", var->name); + parm = strstr(qspos, ":"); + if (parm != NULL) { + parm++; + mparm = apr_palloc(msr->mp, sizeof(msc_parm)); + if (mparm == NULL) + continue; + + mparm->value = apr_pstrmemdup(msr->mp,s->value,s->value_len); + mparm->pad_1 = rule->actionset->arg_min; + mparm->pad_2 = rule->actionset->arg_max; + apr_table_addn(msr->pattern_to_sanitize, parm, (void *)mparm); + } else { + mparm = apr_palloc(msr->mp, sizeof(msc_parm)); + if (mparm == NULL) + continue; + + mparm->value = apr_pstrmemdup(msr->mp,s->value,s->value_len); + apr_table_addn(msr->pattern_to_sanitize, qspos, (void *)mparm); + } + } + } } @@ -2645,10 +2934,10 @@ static int msre_op_verifyCPF_execute(modsec_rec *msr, msre_rule *rule, msre_var return 0; } -/* +/** * \brief Check for a valid SSN * - * \param msr Pointer to the modsec resource + * \param msr ModSecurity transaction source * \param ssnumber Pointer to ssn * \param len ssn length * @@ -2723,11 +3012,11 @@ invalid: return 0; } -/* +/** * \brief Init function to SSN operator * -* \param rule Pointer to the rule -* \param error_msg Pointer to error msg +* \param rule ModSecurity rule struct +* \param error_msg Error message * * \retval 0 On Failure * \retval 1 On Success @@ -2753,13 +3042,13 @@ static int msre_op_verifySSN_init(msre_rule *rule, char **error_msg) { return 1; /* OK */ } -/* +/** * \brief Execution function to SSN operator * -* \param msr Pointer internal modsec request structure -* \param rule Pointer to the rule -* \param var Pointer to variable structure -* \param error_msg Pointer to error msg +* \param msr ModSecurity transaction resource +* \param rule ModSecurity rule struct +* \param var ModSecurity variable struct +* \param error_msg Error message * * \retval -1 On Failure * \retval 1 On Match @@ -2771,9 +3060,13 @@ static int msre_op_verifySSN_execute(modsec_rec *msr, msre_rule *rule, msre_var unsigned int target_length; char *my_error_msg = NULL; int ovector[33]; - int rc; + int rc, jit; int is_ssn = 0; int offset; + int matched_bytes = 0; + char *qspos = NULL; + const char *parm = NULL; + msc_parm *mparm = NULL; if (error_msg == NULL) return -1; *error_msg = NULL; @@ -2785,6 +3078,24 @@ static int msre_op_verifySSN_execute(modsec_rec *msr, msre_rule *rule, msre_var memset(ovector, 0, sizeof(ovector)); + #ifdef WITH_PCRE_STUDY + #ifdef WITH_PCRE_JIT + if (msr->txcfg->debuglog_level >= 4) { + rc = msc_fullinfo(regex, PCRE_INFO_JIT, &jit); + if ((rc != 0) || (jit != 1)) { + *error_msg = apr_psprintf(rule->ruleset->mp, + "Rule %pp [id \"%s\"][file \"%s\"][line \"%d\"] - " + "Execution error - " + "Does not support JIT (%d)", + rule,((rule->actionset != NULL)&&(rule->actionset->id != NULL)) ? rule->actionset->id : "-", + rule->filename != NULL ? rule->filename : "-", + rule->line_num,rc); + msr_log(msr, 4, "%s.", *error_msg); + } + } + #endif + #endif + /* If the given target is null run against an empty * string. This is a behaviour consistent with previous * releases. @@ -2840,6 +3151,10 @@ static int msre_op_verifySSN_execute(modsec_rec *msr, msre_rule *rule, msre_var * and we are done. */ + matched_bytes = apr_table_get(rule->actionset->actions, "sanitizeMatchedBytes") ? 1 : 0; + if(!matched_bytes) + matched_bytes = apr_table_get(rule->actionset->actions, "sanitiseMatchedBytes") ? 1 : 0; + if (apr_table_get(rule->actionset->actions, "capture")) { for(; i < rc; i++) { msc_string *s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); @@ -2856,6 +3171,30 @@ static int msre_op_verifySSN_execute(modsec_rec *msr, msre_rule *rule, msre_var msr_log(msr, 9, "Added regex subexpression to TX.%d: %s", i, log_escape_nq_ex(msr->mp, s->value, s->value_len)); } + + if((matched_bytes == 1) && (var != NULL) && (var->name != NULL)) { + qspos = apr_psprintf(msr->mp, "%s", var->name); + parm = strstr(qspos, ":"); + if (parm != NULL) { + parm++; + mparm = apr_palloc(msr->mp, sizeof(msc_parm)); + if (mparm == NULL) + continue; + + mparm->value = apr_pstrmemdup(msr->mp,s->value,s->value_len); + mparm->pad_1 = rule->actionset->arg_min; + mparm->pad_2 = rule->actionset->arg_max; + apr_table_addn(msr->pattern_to_sanitize, parm, (void *)mparm); + } else { + mparm = apr_palloc(msr->mp, sizeof(msc_parm)); + if (mparm == NULL) + continue; + + mparm->value = apr_pstrmemdup(msr->mp,s->value,s->value_len); + apr_table_addn(msr->pattern_to_sanitize, qspos, (void *)mparm); + } + } + } } @@ -3868,6 +4207,13 @@ void msre_engine_register_default_operators(msre_engine *engine) { msre_op_rx_execute ); + /* validateEncyption */ + msre_engine_op_register(engine, + "validateEncryption", + msre_op_validateEncryption_param_init, + msre_op_validateEncryption_execute + ); + /* pm */ msre_engine_op_register(engine, "pm", diff --git a/apache2/re_tfns.c b/apache2/re_tfns.c index 08a4389d..c7b18436 100644 --- a/apache2/re_tfns.c +++ b/apache2/re_tfns.c @@ -24,6 +24,18 @@ /* cmdline */ +/** +* \brief cmdline transformation function +* +* \param mptmp Pointer to resource pool +* \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_cmdline_execute(apr_pool_t *mptmp, unsigned char *input, long int input_len, char **rval, long int *rval_len) { @@ -302,6 +314,15 @@ static int msre_fn_removeCommentsChar_execute(apr_pool_t *mptmp, unsigned char * } else if ((input[i] == '*')&&(i + 1 < input_len)&&(input[i + 1] == '/')) { changed = 1; i += 2; + } else if ((input[i] == '<')&&(i + 1 < input_len)&&(input[i + 1] == '!')&& + (i + 2 < input_len)&&(input[i+2] == '-')&&(i + 3 < input_len)&& + (input[i + 3] == '-')) { + changed = 1; + i += 4; + } else if ((input[i] == '-')&&(i + 1 < input_len)&&(input[i + 1] == '-')&& + (i + 2 < input_len)&&(input[i+2] == '>')) { + changed = 1; + i += 3; } else if ((input[i] == '-')&&(i + 1 < input_len)&&(input[i + 1] == '-')) { changed = 1; i += 2; @@ -337,16 +358,21 @@ static int msre_fn_removeComments_execute(apr_pool_t *mptmp, unsigned char *inpu changed = 1; incomment = 1; i += 2; - } else if ((input[i] == '-')&&(i + 1 < input_len)&&(input[i + 1] == '-')) { + } else if ((input[i] == '<')&&(i + 1 < input_len)&&(input[i + 1] == '!')&& + (i + 2 < input_len)&&(input[i+2] == '-')&&(i + 3 < input_len)&& + (input[i + 3] == '-') && (incomment == 0)) { + incomment = 1; + changed = 1; + i += 4; + } else if ((input[i] == '-')&&(i + 1 < input_len)&&(input[i + 1] == '-') + && (incomment == 0)) { changed = 1; input[i] = ' '; break; - i += 2; - } else if (input[i] == '#') { + } else if (input[i] == '#' && (incomment == 0)) { changed = 1; input[i] = ' '; break; - i++; } else { input[j] = input[i]; i++; @@ -359,6 +385,13 @@ static int msre_fn_removeComments_execute(apr_pool_t *mptmp, unsigned char *inpu input[j] = input[i]; i++; j++; + } else if ((input[i] == '-')&&(i + 1 < input_len)&&(input[i + 1] == '-')&& + (i + 2 < input_len)&&(input[i+2] == '>')) { + incomment = 0; + i += 3; + input[j] = input[i]; + i++; + j++; } else { i++; } @@ -544,8 +577,18 @@ static int msre_fn_sha1_execute(apr_pool_t *mptmp, unsigned char *input, return 1; } -/* sqlHexDecode */ - +/** +* \brief SqlHexDecode transformation function. Transform xNN data. +* +* \param mptmp Pointer to resource pool +* \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_sqlHexDecode_execute(apr_pool_t *mptmp, unsigned char *input, long int input_len, char **rval, long int *rval_len) { @@ -720,10 +763,10 @@ static int msre_fn_parityOdd7bit_execute(apr_pool_t *mptmp, unsigned char *input return changed; } -/* +/** * \brief Base64 transformation function based on RFC2045 * -* \param mptmp Pointer to resource poil +* \param mptmp Pointer to resource pool * \param input Pointer to input data * \param input_len Input data length * \param rval Pointer to decoded buffer diff --git a/apache2/re_variables.c b/apache2/re_variables.c index be1db4ab..cc5c67b9 100644 --- a/apache2/re_variables.c +++ b/apache2/re_variables.c @@ -675,6 +675,14 @@ static int var_webserver_error_log_generate(modsec_rec *msr, msre_var *var, msre return count; } +#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER > 2 +static int var_useragent_ip_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, + apr_table_t *vartab, apr_pool_t *mptmp) +{ + return var_simple_generate(var, vartab, mptmp, msr->useragent_ip ? msr->useragent_ip : "0.0.0.0"); +} +#endif + /* REMOTE_ADDR */ static int var_remote_addr_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, @@ -1613,6 +1621,50 @@ static int var_perf_logging_generate(modsec_rec *msr, msre_var *var, msre_rule * return generate_performance_variable(msr, var, rule, vartab, mptmp, msr->time_logging); } + +/* PERF_RULES */ + +static int var_perf_rules_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, + apr_table_t *vartab, apr_pool_t *mptmp) +{ + const apr_array_header_t *arr = NULL; + const apr_table_entry_t *te = NULL; + int i, count = 0; + + arr = apr_table_elts(msr->perf_rules); + te = (apr_table_entry_t *)arr->elts; + for (i = 0; i < arr->nelts; i++) { + int match = 0; + + /* Figure out if we want to include this variable. */ + if (var->param == NULL) match = 1; + else { + if (var->param_data != NULL) { /* Regex. */ + char *my_error_msg = NULL; + if (!(msc_regexec((msc_regex_t *)var->param_data, te[i].key, + strlen(te[i].key), &my_error_msg) == PCRE_ERROR_NOMATCH)) match = 1; + } else { /* Simple comparison. */ + if (strcasecmp(te[i].key, var->param) == 0) match = 1; + } + } + + /* If we had a match add this argument to the collection. */ + if (match) { + msre_var *rvar = apr_pmemdup(mptmp, var, sizeof(msre_var)); + + rvar->value = te[i].val; + rvar->value_len = strlen(rvar->value); + rvar->name = apr_psprintf(mptmp, "PERF_RULES:%s", + log_escape_nq(mptmp, te[i].key)); + apr_table_addn(vartab, rvar->name, (void *)rvar); + + count++; + } + } + + return count; +} + /* DURATION */ static int var_duration_generate(modsec_rec *msr, msre_var *var, msre_rule *rule, @@ -1622,9 +1674,8 @@ static int var_duration_generate(modsec_rec *msr, msre_var *var, msre_rule *rule rvar = apr_pmemdup(mptmp, var, sizeof(msre_var)); rvar->value = apr_psprintf(mptmp, "%" APR_TIME_T_FMT, - (apr_time_msec(apr_time_now() - msr->r->request_time))); + (apr_time_usec(apr_time_now() - msr->r->request_time))); rvar->value_len = strlen(rvar->value); - apr_table_addn(vartab, rvar->name, (void *)rvar); return 1; @@ -1875,7 +1926,7 @@ static int var_matched_vars_names_generate(modsec_rec *msr, msre_var *var, msre_ rvar->value = apr_pstrndup(mptmp, str->name, strlen(str->name)); rvar->value_len = strlen(rvar->value); - rvar->name = apr_psprintf(mptmp, "MATCHED_VARS_NAMES:%s", + rvar->name = apr_psprintf(mptmp, "%s", log_escape_nq(mptmp, str->name)); if(var->is_counting == 0) @@ -1891,7 +1942,7 @@ static int var_matched_vars_names_generate(modsec_rec *msr, msre_var *var, msre_ apr_table_addn(vartab, rvar->name, (void *)rvar); if (msr->txcfg->debuglog_level >= 9) { - msr_log(msr, 9, "Set variable \"%s\" value \"%s\" size %d to collection.", rvar->name, rvar->value, rvar->value_len); + msr_log(msr, 9, "Set variable \"%s\" size %d to collection.", rvar->name,rvar->value_len); } count++; @@ -1935,7 +1986,7 @@ static int var_matched_vars_generate(modsec_rec *msr, msre_var *var, msre_rule * rvar->value = apr_pstrndup(mptmp, str->value, str->value_len); rvar->value_len = str->value_len; - rvar->name = apr_psprintf(mptmp, "MATCHED_VARS:%s", + rvar->name = apr_psprintf(mptmp, "%s", log_escape_nq(mptmp, str->name)); if(var->is_counting == 0) @@ -2913,6 +2964,19 @@ void msre_engine_register_default_variables(msre_engine *engine) { PHASE_REQUEST_HEADERS ); +#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER > 2 + /* USERAGENT_IP */ + msre_engine_variable_register(engine, + "USERAGENT_IP", + VAR_SIMPLE, + 0, 0, + NULL, + var_useragent_ip_generate, + VAR_CACHE, + PHASE_REQUEST_HEADERS + ); +#endif + /* REMOTE_ADDR */ msre_engine_variable_register(engine, "REMOTE_ADDR", @@ -3508,7 +3572,18 @@ void msre_engine_register_default_variables(msre_engine *engine) { VAR_DONT_CACHE, /* dynamic */ PHASE_RESPONSE_HEADERS ); - + + /* PERF_RULES */ + msre_engine_variable_register(engine, + "PERF_RULES", + VAR_LIST, + 0, 1, + var_generic_list_validate, + var_perf_rules_generate, + VAR_DONT_CACHE, + PHASE_REQUEST_HEADERS + ); + /* PERF_ALL */ msre_engine_variable_register(engine, "PERF_ALL", @@ -3519,7 +3594,7 @@ void msre_engine_register_default_variables(msre_engine *engine) { VAR_DONT_CACHE, PHASE_REQUEST_HEADERS ); - + /* PERF_COMBINED */ msre_engine_variable_register(engine, "PERF_COMBINED", @@ -3530,7 +3605,7 @@ void msre_engine_register_default_variables(msre_engine *engine) { VAR_DONT_CACHE, PHASE_REQUEST_HEADERS ); - + /* PERF_GC */ msre_engine_variable_register(engine, "PERF_GC", @@ -3541,7 +3616,7 @@ void msre_engine_register_default_variables(msre_engine *engine) { VAR_DONT_CACHE, PHASE_REQUEST_HEADERS ); - + /* PERF_LOGGING */ msre_engine_variable_register(engine, "PERF_LOGGING", @@ -3552,7 +3627,7 @@ void msre_engine_register_default_variables(msre_engine *engine) { VAR_DONT_CACHE, PHASE_REQUEST_HEADERS ); - + /* PERF_PHASE1 */ msre_engine_variable_register(engine, "PERF_PHASE1", @@ -3563,7 +3638,7 @@ void msre_engine_register_default_variables(msre_engine *engine) { VAR_DONT_CACHE, PHASE_REQUEST_HEADERS ); - + /* PERF_PHASE2 */ msre_engine_variable_register(engine, "PERF_PHASE2", @@ -3574,7 +3649,7 @@ void msre_engine_register_default_variables(msre_engine *engine) { VAR_DONT_CACHE, PHASE_REQUEST_HEADERS ); - + /* PERF_PHASE3 */ msre_engine_variable_register(engine, "PERF_PHASE3", @@ -3585,7 +3660,7 @@ void msre_engine_register_default_variables(msre_engine *engine) { VAR_DONT_CACHE, PHASE_REQUEST_HEADERS ); - + /* PERF_PHASE4 */ msre_engine_variable_register(engine, "PERF_PHASE4", @@ -3596,7 +3671,7 @@ void msre_engine_register_default_variables(msre_engine *engine) { VAR_DONT_CACHE, PHASE_REQUEST_HEADERS ); - + /* PERF_PHASE5 */ msre_engine_variable_register(engine, "PERF_PHASE5", @@ -3607,7 +3682,7 @@ void msre_engine_register_default_variables(msre_engine *engine) { VAR_DONT_CACHE, PHASE_REQUEST_HEADERS ); - + /* PERF_SREAD */ msre_engine_variable_register(engine, "PERF_SREAD", @@ -3618,7 +3693,7 @@ void msre_engine_register_default_variables(msre_engine *engine) { VAR_DONT_CACHE, PHASE_REQUEST_HEADERS ); - + /* PERF_SWRITE */ msre_engine_variable_register(engine, "PERF_SWRITE", @@ -3629,7 +3704,7 @@ void msre_engine_register_default_variables(msre_engine *engine) { VAR_DONT_CACHE, PHASE_REQUEST_HEADERS ); - + /* DURATION */ msre_engine_variable_register(engine, "DURATION", diff --git a/configure.ac b/configure.ac index b9c4e8bf..5cba364c 100644 --- a/configure.ac +++ b/configure.ac @@ -6,7 +6,7 @@ dnl AC_PREREQ(2.63) -AC_INIT([modsecurity], [2.6], [support@modsecurity.org]) +AC_INIT([modsecurity], [2.7], [support@modsecurity.org]) AC_CONFIG_MACRO_DIR([build]) AC_CONFIG_SRCDIR([LICENSE]) @@ -304,6 +304,38 @@ AC_ARG_ENABLE(pcre-match-limit-recursion, pcre_match_limit_recursion='-DMODSEC_PCRE_MATCH_LIMIT_RECURSION=1500' ]) +# Enable Lua per transaction cache +AC_ARG_ENABLE(lua-cache, + AS_HELP_STRING([--enable-lua-cache], + [Enable Lua per transaction cache.]), +[ + if test "$enableval" != "no"; then + lua_cache="-DCACHE_LUA" + MODSEC_EXTRA_CFLAGS="$MODSEC_EXTRA_CFLAGS $lua_cache" + else + lua_cache= + fi +], +[ + lua_cache= +]) + +# Enable phase-1 in post_read_request +AC_ARG_ENABLE(request-early, + AS_HELP_STRING([--enable-request-early], + [Place phase1 into post_read_request hook.]), +[ + if test "$enableval" != "no"; then + request_early="-DREQUEST_EARLY" + MODSEC_EXTRA_CFLAGS="$MODSEC_EXTRA_CFLAGS $request_early" + else + request_early= + fi +], +[ + request_early='-DREQUEST_EARLY' +]) + # Ignore configure errors AC_ARG_ENABLE(errors, AS_HELP_STRING([--disable-errors], @@ -567,7 +599,7 @@ else fi fi -MODSEC_EXTRA_CFLAGS="$pcre_study $pcre_match_limit $pcre_match_limit_recursion $pcre_jit $debug_conf $debug_cache $debug_acmp $debug_mem $perf_meas $modsec_api" +MODSEC_EXTRA_CFLAGS="$pcre_study $pcre_match_limit $pcre_match_limit_recursion $pcre_jit $request_early $lua_cache $debug_conf $debug_cache $debug_acmp $debug_mem $perf_meas $modsec_api" APXS_WRAPPER=build/apxs-wrapper APXS_EXTRA_CFLAGS=""