From 0d24a08f33b55db5881c135d6c98b47591cfad83 Mon Sep 17 00:00:00 2001 From: brectanus Date: Sat, 19 Jan 2008 02:23:41 +0000 Subject: [PATCH] Implemented SecRuleUpdateActionById. See #442. --- CHANGES | 2 + apache2/Makefile.in | 6 +- apache2/apache2_config.c | 128 ++++++++++++++++++++++++++ apache2/re.c | 49 +++++++++- apache2/re.h | 6 +- doc/modsecurity2-apache-reference.xml | 34 +++++++ 6 files changed, 220 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index 7472ad66..22ca350f 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,8 @@ 02 Jan 2008 - 2.5.0-rc2 ----------------------- + * Implemented SecRuleUpdateActionById. + * Phase 5 rules can now be removed via SecRuleRemoveBy* directives. * Build is now 'configure' based (autotools). diff --git a/apache2/Makefile.in b/apache2/Makefile.in index 68a4233e..51d6a86c 100644 --- a/apache2/Makefile.in +++ b/apache2/Makefile.in @@ -98,11 +98,13 @@ test: t/run-tests.pl msc_test mlogc: @$(MAKE) -C mlogc-src mlogc \ && cp -p mlogc-src/mlogc . \ - && echo "mlogc-src/INSTALL" \ + && echo \ + && echo "See: mlogc-src/INSTALL" \ && echo mlogc-static: @$(MAKE) -C mlogc-src static \ && cp -p mlogc-src/mlogc mlogc-static \ - && echo "mlogc-src/INSTALL" \ + && echo \ + && echo "See: mlogc-src/INSTALL" \ && echo diff --git a/apache2/apache2_config.c b/apache2/apache2_config.c index b553c9f6..846a0c80 100644 --- a/apache2/apache2_config.c +++ b/apache2/apache2_config.c @@ -742,6 +742,120 @@ static const char *add_marker(cmd_parms *cmd, directory_config *dcfg, const char return NULL; } +/** + * + */ +static const char *update_rule_action(cmd_parms *cmd, directory_config *dcfg, + const char *p1, const char *p2) +{ + char *my_error_msg = NULL; + msre_rule *rule = NULL; + msre_actionset *new_actionset = NULL; + msre_ruleset *ruleset = dcfg->ruleset; + extern msc_engine *modsecurity; + + /* Get the ruleset if one exists */ + if ((ruleset == NULL)||(ruleset == NOT_SET_P)) { + return NULL; + } + + #ifdef DEBUG_CONF + ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool, + "Looking to update rule id=\"%s\" with \"%s\".", p1, p2); + #endif + + /* Fetch the rule */ + rule = msre_ruleset_fetch_rule(ruleset, p1); + if (rule == NULL) { + #ifdef DEBUG_CONF + ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool, + "Failed to update rule id=\"%s\" with \"%s\": Rule not found.", p1, p2); + #endif + return NULL; + } + + /* Check the rule actionset */ + /* ENH: Can this happen? */ + if (rule->actionset == NULL) { + return apr_psprintf(cmd->pool, "ModSecurity: Attempt to update action for rule \"%s\" failed: Rule does not have an actionset.", p1); + } + + /* Create a new actionset */ + new_actionset = msre_actionset_create(modsecurity->msre, p2, &my_error_msg); + if (dcfg->tmp_default_actionset == NULL) return FATAL_ERROR; + if (my_error_msg != NULL) return my_error_msg; + + /* Must NOT change an id */ + if ((new_actionset->id != NOT_SET_P) && (rule->actionset->id != NULL) && (strcmp(rule->actionset->id, new_actionset->id) != 0)) { + return apr_psprintf(cmd->pool, "ModSecurity: Rule IDs cannot be updated via SecRuleUpdateActionById."); + } + + /* Must NOT alter the phase */ + if ((new_actionset->phase != NOT_SET) && (rule->actionset->phase != new_actionset->phase)) { + return apr_psprintf(cmd->pool, "ModSecurity: Rule phases cannot be updated via SecRuleUpdateActionById."); + } + + #ifdef DEBUG_CONF + { + const apr_array_header_t *tarr = apr_table_elts(rule->actionset->actions); + const apr_table_entry_t *telts = (const apr_table_entry_t*)tarr->elts; + char *actions = NULL; + int i; + for (i = 0; i < tarr->nelts; i++) { + msre_action *action = (msre_action *)telts[i].val; + actions = apr_pstrcat(ruleset->mp, + (actions == NULL) ? "" : actions, + (actions == NULL) ? "" : ",", + action->metadata->name, + (action->param == NULL) ? "" : ":'", + (action->param == NULL) ? "" : action->param, + (action->param == NULL) ? "" : "'", + NULL); + } + ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool, + "Updating rule %pp id=\"%s\" action: \"%s\"", + rule, + (rule->actionset->id == NOT_SET_P ? "(none)" : rule->actionset->id), + actions); + } + #endif + + /* Merge new actions with the rule */ + /* ENH: Will this leak the old actionset? */ + rule->actionset = msre_actionset_merge(modsecurity->msre, rule->actionset, + new_actionset, 1); + msre_actionset_set_defaults(rule->actionset); + + /* ENH: Change the unparsed string, but may be impossible. */ + + #ifdef DEBUG_CONF + { + const apr_array_header_t *tarr = apr_table_elts(rule->actionset->actions); + const apr_table_entry_t *telts = (const apr_table_entry_t*)tarr->elts; + char *actions = NULL; + int i; + for (i = 0; i < tarr->nelts; i++) { + msre_action *action = (msre_action *)telts[i].val; + actions = apr_pstrcat(ruleset->mp, + (actions == NULL) ? "" : actions, + (actions == NULL) ? "" : ",", + action->metadata->name, + (action->param == NULL) ? "" : ":'", + (action->param == NULL) ? "" : action->param, + (action->param == NULL) ? "" : "'", + NULL); + } + ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, cmd->pool, + "Updated rule %pp id=\"%s\" action: \"%s\"", + rule, + (rule->actionset->id == NOT_SET_P ? "(none)" : rule->actionset->id), + actions); + } + #endif + + return NULL; +} + /* -- Configuration directives -- */ static const char *cmd_action(cmd_parms *cmd, void *_dcfg, const char *p1) { @@ -1334,6 +1448,12 @@ static const char *cmd_rule_remove_by_msg(cmd_parms *cmd, void *_dcfg, const cha return NULL; } +static const char *cmd_rule_update_action_by_id(cmd_parms *cmd, void *_dcfg, + const char *p1, const char *p2) +{ + return update_rule_action(cmd, (directory_config *)_dcfg, p1, p2); +} + static const char *cmd_server_signature(cmd_parms *cmd, void *_dcfg, const char *p1) { if (cmd->server->is_virtual) { return "ModSecurity: SecServerSignature not allowed in VirtualHost"; @@ -1916,6 +2036,14 @@ const command_rec module_directives[] = { "rule message for removal" ), + AP_INIT_TAKE2 ( + "SecRuleUpdateActionById", + cmd_rule_update_action_by_id, + NULL, + CMD_SCOPE_ANY, + "updated action list" + ), + AP_INIT_TAKE1 ( "SecServerSignature", cmd_server_signature, diff --git a/apache2/re.c b/apache2/re.c index e1ec3c57..80f8388d 100644 --- a/apache2/re.c +++ b/apache2/re.c @@ -559,7 +559,7 @@ msre_actionset *msre_actionset_create_default(msre_engine *engine) { /** * Sets the default values for the hard-coded actionset configuration. */ -static void msre_actionset_set_defaults(msre_actionset *actionset) { +void msre_actionset_set_defaults(msre_actionset *actionset) { /* Metadata */ if (actionset->id == NOT_SET_P) actionset->id = NULL; if (actionset->rev == NOT_SET_P) actionset->rev = NULL; @@ -1027,6 +1027,53 @@ int msre_ruleset_rule_add(msre_ruleset *ruleset, msre_rule *rule, int phase) { return 1; } +static msre_rule * msre_ruleset_fetch_phase_rule(const msre_ruleset *ruleset, const char *id, + const apr_array_header_t *phase_arr) +{ + msre_rule **rules = (msre_rule **)phase_arr->elts; + int i; + + for (i = 0; i < phase_arr->nelts; i++) { + msre_rule *rule = (msre_rule *)rules[i]; + + if ( (rule->actionset != NULL) + && !rule->actionset->is_chained + && (rule->actionset->id != NULL) + && (strcmp(rule->actionset->id, id) == 0)) + { + /* Return rule that matched unless it is a placeholder */ + return (rule->placeholder == RULE_PH_NONE) ? rule : NULL; + } + } + + return NULL; +} + +/** + * Fetches rule from the ruleset all rules that match the given exception. + */ +msre_rule * msre_ruleset_fetch_rule(msre_ruleset *ruleset, const char *id) { + msre_rule *rule = NULL; + + if (ruleset == NULL) return NULL; + + rule = msre_ruleset_fetch_phase_rule(ruleset, id, ruleset->phase_request_headers); + if (rule != NULL) return rule; + + rule = msre_ruleset_fetch_phase_rule(ruleset, id, ruleset->phase_request_body); + if (rule != NULL) return rule; + + rule = msre_ruleset_fetch_phase_rule(ruleset, id, ruleset->phase_response_headers); + if (rule != NULL) return rule; + + rule = msre_ruleset_fetch_phase_rule(ruleset, id, ruleset->phase_response_body); + if (rule != NULL) return rule; + + rule = msre_ruleset_fetch_phase_rule(ruleset, id, ruleset->phase_logging); + + return rule; +} + static int msre_ruleset_phase_rule_remove_with_exception(msre_ruleset *ruleset, rule_exception *re, apr_array_header_t *phase_arr) { diff --git a/apache2/re.h b/apache2/re.h index edeffab4..2be763da 100644 --- a/apache2/re.h +++ b/apache2/re.h @@ -106,6 +106,8 @@ msre_ruleset DSOLOCAL *msre_ruleset_create(msre_engine *engine, apr_pool_t *mp); int DSOLOCAL msre_ruleset_rule_add(msre_ruleset *ruleset, msre_rule *rule, int phase); +msre_rule DSOLOCAL *msre_ruleset_fetch_rule(msre_ruleset *ruleset, const char *id); + int DSOLOCAL msre_ruleset_rule_remove_with_exception(msre_ruleset *ruleset, rule_exception *re); /* @@ -159,8 +161,6 @@ msre_rule DSOLOCAL *msre_rule_lua_create(msre_ruleset *ruleset, const char *fn, int line, const char *script_filename, const char *actions, char **error_msg); -void DSOLOCAL msre_rule_actionset_init(msre_rule *rule); - apr_status_t DSOLOCAL msre_rule_process(msre_rule *rule, modsec_rec *msr); #define VAR_SIMPLE 0 /* REQUEST_URI */ @@ -287,6 +287,8 @@ msre_actionset DSOLOCAL *msre_actionset_merge(msre_engine *engine, msre_actionse msre_actionset DSOLOCAL *msre_actionset_create_default(msre_engine *engine); +void DSOLOCAL msre_actionset_set_defaults(msre_actionset *actionset); + void DSOLOCAL msre_actionset_init(msre_actionset *actionset, msre_rule *rule); typedef char *(*fn_action_validate_t)(msre_engine *engine, msre_action *action); diff --git a/doc/modsecurity2-apache-reference.xml b/doc/modsecurity2-apache-reference.xml index eb0c904f..0db38a1d 100644 --- a/doc/modsecurity2-apache-reference.xml +++ b/doc/modsecurity2-apache-reference.xml @@ -1897,6 +1897,40 @@ end +
+ <literal>SecRuleUpdateActionById</literal> + + Description: Updates the action list of the + specified rule. + + Syntax: SecRuleRemoveById RULEID ACTIONLIST + + Example Usage: SecRuleUpdateActionById 12345 + deny,status:403 + + Processing Phase: Any + + Scope: Any + + Dependencies/Notes: This directive merges the + specified action list with the rule's action list. There are two + limitations. The rule ID cannot be changed, nor can the phase. Further + note that actions that may be specified multiple times are appended to + the original. + + SecAction \ + "t:lowercase,phase:2,id:12345,pass,msg:'The Message',log,auditlog" +SecRuleUpdateActionById 12345 "t:compressWhitespace,deny,status:403,msg:'A new message' + + The example above will cause the rule to be executed as if it was + specified as follows: + + SecAction \ + "t:lowercase,phase:2,id:12345,log,auditlog,t:compressWhitespace,deny,status:403,msg:'A new message'" +
+
<literal>SecServerSignature</literal>