2 * ModSecurity for Apache 2.x, http://www.modsecurity.org/
3 * Copyright (c) 2004-2009 Breach Security, Inc. (http://www.breach.com/)
5 * This product is released under the terms of the General Public Licence,
6 * version 2 (GPLv2). Please refer to the file LICENSE (included with this
7 * distribution) which contains the complete text of the licence.
9 * There are special exceptions to the terms and conditions of the GPL
10 * as it is applied to this software. View the full text of the exception in
11 * file MODSECURITY_LICENSING_EXCEPTION in the directory of this software
14 * If any of the files related to licensing are missing or if you have any
15 * other questions related to licensing please contact Breach Security, Inc.
16 * directly using the email address support@breach.com.
27 static const char *const severities[] = {
39 /* -- Actions, variables, functions and operator functions ----------------- */
42 * Remove actions with the same cardinality group from the actionset.
44 static void msre_actionset_cardinality_fixup(msre_actionset *actionset, msre_action *action) {
45 const apr_array_header_t *tarr = NULL;
46 const apr_table_entry_t *telts = NULL;
49 if ((actionset == NULL) || (action == NULL)) return;
51 tarr = apr_table_elts(actionset->actions);
52 telts = (const apr_table_entry_t*)tarr->elts;
54 for (i = 0; i < tarr->nelts; i++) {
55 msre_action *target = (msre_action *)telts[i].val;
56 if (target->metadata->cardinality_group == action->metadata->cardinality_group) {
58 apr_table_unset(actionset->actions, target->metadata->name);
64 * Generate an action string from an actionset.
66 char *msre_actionset_generate_action_string(apr_pool_t *pool, const msre_actionset *actionset)
68 const apr_array_header_t *tarr = NULL;
69 const apr_table_entry_t *telts = NULL;
74 if (actionset == NULL) return NULL;
76 chain = ((actionset->rule != NOT_SET_P) && actionset->rule->chain_starter) ? 1 : 0;
78 tarr = apr_table_elts(actionset->actions);
79 telts = (const apr_table_entry_t*)tarr->elts;
81 for (i = 0; i < tarr->nelts; i++) {
82 msre_action *action = (msre_action *)telts[i].val;
86 /* Skip some actions that are not used in a chain. */
87 if ( (action->metadata->type == ACTION_DISRUPTIVE)
88 || (action->metadata->type == ACTION_METADATA)
89 || (strcmp("log", action->metadata->name) == 0)
90 || (strcmp("auditlog", action->metadata->name) == 0)
91 || (strcmp("nolog", action->metadata->name) == 0)
92 || (strcmp("noauditlog", action->metadata->name) == 0)
93 || (strcmp("severity", action->metadata->name) == 0)
94 || (strcmp("tag", action->metadata->name) == 0)
95 || (strcmp("phase", action->metadata->name) == 0))
101 /* Check if we need any quotes */
102 if (action->param != NULL) {
104 for(j = 0; action->param[j] != '\0'; j++) {
105 if (isspace(action->param[j])) {
110 if (j == 0) use_quotes = 1;
113 actions = apr_pstrcat(pool,
114 (actions == NULL) ? "" : actions,
115 (actions == NULL) ? "" : ",",
116 action->metadata->name,
117 (action->param == NULL) ? "" : ":",
118 (use_quotes) ? "'" : "",
119 (action->param == NULL) ? "" : action->param,
120 (use_quotes) ? "'" : "",
128 * Add an action to an actionset.
130 static void msre_actionset_action_add(msre_actionset *actionset, msre_action *action)
132 msre_action *add_action = action;
134 if ((actionset == NULL)) return;
137 * The "block" action is just a placeholder for the parent action.
139 if ((actionset->parent_intercept_action_rec != NULL) && (actionset->parent_intercept_action_rec != NOT_SET_P) && (strcmp("block", action->metadata->name) == 0) && (strcmp("block", action->metadata->name) == 0)) {
140 /* revert back to parent */
141 actionset->intercept_action = actionset->parent_intercept_action;
142 add_action = actionset->parent_intercept_action_rec;
145 if ((add_action == NULL)) return;
147 if (add_action->metadata->cardinality_group != ACTION_CGROUP_NONE) {
148 msre_actionset_cardinality_fixup(actionset, add_action);
151 if (add_action->metadata->cardinality == ACTION_CARDINALITY_ONE) {
152 /* One action per actionlist. */
153 apr_table_setn(actionset->actions, add_action->metadata->name, (void *)add_action);
155 /* Multiple actions per actionlist. */
156 apr_table_addn(actionset->actions, add_action->metadata->name, (void *)add_action);
161 * Creates msre_var instances (rule variables) out of the
162 * given text string and places them into the supplied table.
164 apr_status_t msre_parse_targets(msre_ruleset *ruleset, const char *text,
165 apr_array_header_t *arr, char **error_msg)
167 const apr_array_header_t *tarr;
168 const apr_table_entry_t *telts;
169 apr_table_t *vartable;
170 unsigned int count = 0;
175 if (text == NULL) return -1;
177 /* Extract name & value pairs first */
178 vartable = apr_table_make(ruleset->mp, 10);
179 if (vartable == NULL) return -1;
180 rc = msre_parse_generic(ruleset->mp, text, vartable, error_msg);
181 if (rc < 0) return rc;
183 /* Loop through the table and create variables */
184 tarr = apr_table_elts(vartable);
185 telts = (const apr_table_entry_t*)tarr->elts;
186 for (i = 0; i < tarr->nelts; i++) {
187 var = msre_create_var(ruleset, telts[i].key, telts[i].val, NULL, error_msg);
188 if (var == NULL) return -1;
189 *(msre_var **)apr_array_push(arr) = var;
197 * Creates msre_action instances by parsing the given string, placing
198 * them into the supplied array.
200 apr_status_t msre_parse_actions(msre_engine *engine, msre_actionset *actionset,
201 const char *text, char **error_msg)
203 const apr_array_header_t *tarr;
204 const apr_table_entry_t *telts;
205 apr_table_t *vartable;
206 unsigned int count = 0;
211 if (text == NULL) return -1;
213 /* Extract name & value pairs first */
214 vartable = apr_table_make(engine->mp, 10);
215 if (vartable == NULL) return -1;
216 rc = msre_parse_generic(engine->mp, text, vartable, error_msg);
217 if (rc < 0) return rc;
219 /* Loop through the table and create actions */
220 tarr = apr_table_elts(vartable);
221 telts = (const apr_table_entry_t*)tarr->elts;
222 for (i = 0; i < tarr->nelts; i++) {
224 action = msre_create_action(engine, telts[i].key, telts[i].val, error_msg);
225 if (action == NULL) return -1;
227 /* Initialise action (option). */
228 if (action->metadata->init != NULL) {
229 action->metadata->init(engine, actionset, action);
232 msre_actionset_action_add(actionset, action);
241 * Locates variable metadata given the variable name.
243 msre_var_metadata *msre_resolve_var(msre_engine *engine, const char *name) {
244 return (msre_var_metadata *)apr_table_get(engine->variables, name);
248 * Locates action metadata given the action name.
250 msre_action_metadata *msre_resolve_action(msre_engine *engine, const char *name) {
251 return (msre_action_metadata *)apr_table_get(engine->actions, name);
255 * Creates a new variable instance given the variable name
256 * and an (optional) parameter.
258 msre_var *msre_create_var_ex(apr_pool_t *pool, msre_engine *engine, const char *name, const char *param,
259 modsec_rec *msr, char **error_msg)
261 const char *varparam = param;
262 msre_var *var = apr_pcalloc(pool, sizeof(msre_var));
263 if (var == NULL) return NULL;
265 if (error_msg == NULL) return NULL;
268 /* Handle negation and member counting */
269 if (name[0] == '!') {
271 var->name = name + 1;
274 if (name[0] == '&') {
275 var->is_counting = 1;
276 var->name = name + 1;
282 /* Treat HTTP_* targets as an alias for REQUEST_HEADERS:* */
283 if ( (var->name != NULL)
284 && (strlen(var->name) > 5)
285 && (strncmp("HTTP_", var->name, 5) == 0))
287 const char *oldname = var->name;
288 var->name = apr_pstrdup(pool, "REQUEST_HEADERS");
289 varparam = apr_pstrdup(pool, oldname + 5);
293 /* Resolve variable */
294 var->metadata = msre_resolve_var(engine, var->name);
295 if (var->metadata == NULL) {
296 *error_msg = apr_psprintf(engine->mp, "Unknown variable: %s", name);
300 /* The counting operator "&" can only be used against collections. */
301 if (var->is_counting) {
302 if (var->metadata->type == VAR_SIMPLE) {
303 *error_msg = apr_psprintf(engine->mp, "The & modificator does not apply to "
304 "non-collection variables.");
309 /* Check the parameter. */
310 if (varparam == NULL) {
311 if (var->metadata->argc_min > 0) {
312 *error_msg = apr_psprintf(engine->mp, "Missing mandatory parameter for variable %s.",
316 } else { /* Parameter present */
318 /* Do we allow a parameter? */
319 if (var->metadata->argc_max == 0) {
320 *error_msg = apr_psprintf(engine->mp, "Variable %s does not support parameters.",
325 var->param = varparam;
332 * Create a new variable object from the provided name and value.
334 * NOTE: this allocates out of the global pool and should not be used
337 msre_var *msre_create_var(msre_ruleset *ruleset, const char *name, const char *param,
338 modsec_rec *msr, char **error_msg)
340 msre_var *var = msre_create_var_ex(ruleset->engine->mp, ruleset->engine, name, param, msr, error_msg);
341 if (var == NULL) return NULL;
343 /* Validate & initialise variable */
344 if (var->metadata->validate != NULL) {
345 *error_msg = var->metadata->validate(ruleset, var);
346 if (*error_msg != NULL) {
355 * Creates a new action instance given its name and an (optional) parameter.
357 msre_action *msre_create_action(msre_engine *engine, const char *name, const char *param,
360 msre_action *action = apr_pcalloc(engine->mp, sizeof(msre_action));
361 if (action == NULL) return NULL;
363 if (error_msg == NULL) return NULL;
367 action->metadata = msre_resolve_action(engine, name);
368 if (action->metadata == NULL) {
369 *error_msg = apr_psprintf(engine->mp, "Unknown action: %s", name);
373 if (param == NULL) { /* Parameter not present */
374 if (action->metadata->argc_min > 0) {
375 *error_msg = apr_psprintf(engine->mp, "Missing mandatory parameter for action %s",
379 } else { /* Parameter present */
381 /* Should we allow the parameter? */
382 if (action->metadata->argc_max == 0) {
383 *error_msg = apr_psprintf(engine->mp, "Extra parameter provided to action %s", name);
387 /* Handle +/- modificators */
388 if ((param[0] == '+')||(param[0] == '-')) {
389 if (action->metadata->allow_param_plusminus == 0) {
390 *error_msg = apr_psprintf(engine->mp,
391 "Action %s does not allow +/- modificators.", name);
394 else { /* Modificators allowed. */
395 if (param[0] == '+') {
396 action->param = param + 1;
397 action->param_plusminus = POSITIVE_VALUE;
399 if (param[0] == '-') {
400 action->param = param + 1;
401 action->param_plusminus = NEGATIVE_VALUE;
405 action->param = param;
408 /* Validate parameter */
409 if (action->metadata->validate != NULL) {
410 *error_msg = action->metadata->validate(engine, action);
411 if (*error_msg != NULL) return NULL;
419 * Generic parser that is used as basis for target and action parsing.
420 * It breaks up the input string into name-parameter pairs and places
421 * them into the given table.
423 int msre_parse_generic(apr_pool_t *mp, const char *text, apr_table_t *vartable,
426 char *p = (char *)text;
429 if (error_msg == NULL) return -1;
434 char *name = NULL, *value = NULL;
436 /* ignore whitespace */
437 while(isspace(*p)) p++;
438 if (*p == '\0') return count;
440 /* we are at the beginning of the name */
442 while((*p != '\0')&&(*p != '|')&&(*p != ':')&&(*p != ',')&&(!isspace(*p))) p++; /* ENH replace with isvarnamechar() */
445 name = apr_pstrmemdup(mp, name, p - name);
447 if (*p != ':') { /* we don't have a parameter */
448 /* add to the table with no value */
449 apr_table_addn(vartable, name, NULL);
452 /* go over any whitespace present */
453 while(isspace(*p)) p++;
460 /* skip over the separator character and continue */
461 if ((*p == ',')||(*p == '|')) {
466 *error_msg = apr_psprintf(mp, "Unexpected character at position %d: %s",
467 (int)(p - text), text);
471 /* we have a parameter */
473 p++; /* move over the colon */
475 /* we'll allow empty values */
477 apr_table_addn(vartable, name, NULL);
482 if ((*p == ',')||(*p == '|')) {
483 apr_table_addn(vartable, name, NULL);
485 /* move over the separator char and continue */
490 /* we really have a parameter */
492 if (*p == '\'') { /* quoted value */
495 p++; /* go over the openning quote */
496 value = d = strdup(p);
497 if (d == NULL) return -1;
501 *error_msg = apr_psprintf(mp, "Missing closing quote at position %d: %s",
502 (int)(p - text), text);
507 if ( (*(p + 1) == '\0') || ((*(p + 1) != '\'')&&(*(p + 1) != '\\')) ) {
508 *error_msg = apr_psprintf(mp, "Invalid quoted pair at position %d: %s",
509 (int)(p - text), text);
527 value = apr_pstrdup(mp, d);
529 } else { /* non-quoted value */
531 while((*p != '\0')&&(*p != ',')&&(*p != '|')&&(!isspace(*p))) p++;
532 value = apr_pstrmemdup(mp, value, p - value);
536 apr_table_addn(vartable, name, value);
539 /* move to the first character of the next name-value pair */
540 while(isspace(*p)||(*p == ',')||(*p == '|')) p++;
547 /* -- Actionset functions -------------------------------------------------- */
550 * Creates an actionset instance and (as an option) populates it by
551 * parsing the given string which contains a list of actions.
553 msre_actionset *msre_actionset_create(msre_engine *engine, const char *text,
556 msre_actionset *actionset = (msre_actionset *)apr_pcalloc(engine->mp,
557 sizeof(msre_actionset));
558 if (actionset == NULL) return NULL;
560 actionset->actions = apr_table_make(engine->mp, 25);
561 if (actionset->actions == NULL) return NULL;
564 actionset->id = NOT_SET_P;
565 actionset->rev = NOT_SET_P;
566 actionset->msg = NOT_SET_P;
567 actionset->logdata = NOT_SET_P;
568 actionset->phase = NOT_SET;
569 actionset->severity = -1;
570 actionset->rule = NOT_SET_P;
573 actionset->is_chained = NOT_SET;
574 actionset->skip_count = NOT_SET;
575 actionset->skip_after = NOT_SET_P;
578 actionset->parent_intercept_action_rec = NOT_SET_P;
579 actionset->intercept_action_rec = NOT_SET_P;
580 actionset->parent_intercept_action = NOT_SET;
581 actionset->intercept_action = NOT_SET;
582 actionset->intercept_uri = NOT_SET_P;
583 actionset->intercept_status = NOT_SET;
584 actionset->intercept_pause = NOT_SET;
587 actionset->auditlog = NOT_SET;
588 actionset->log = NOT_SET;
590 /* Parse the list of actions, if it's present */
592 if (msre_parse_actions(engine, actionset, text, error_msg) < 0) {
601 * Create a (shallow) copy of the supplied actionset.
603 static msre_actionset *msre_actionset_copy(apr_pool_t *mp, msre_actionset *orig) {
604 msre_actionset *copy = NULL;
606 if (orig == NULL) return NULL;
607 copy = (msre_actionset *)apr_pmemdup(mp, orig, sizeof(msre_actionset));
608 if (copy == NULL) return NULL;
609 copy->actions = apr_table_copy(mp, orig->actions);
615 * Merges two actionsets into one.
617 msre_actionset *msre_actionset_merge(msre_engine *engine, msre_actionset *parent,
618 msre_actionset *child, int inherit_by_default)
620 msre_actionset *merged = NULL;
621 const apr_array_header_t *tarr;
622 const apr_table_entry_t *telts;
625 if (inherit_by_default == 0) {
626 /* There is nothing to merge in this case. */
627 return msre_actionset_copy(engine->mp, child);
630 /* Start with a copy of the parent configuration. */
631 merged = msre_actionset_copy(engine->mp, parent);
632 if (merged == NULL) return NULL;
635 /* The child actionset does not exist, hence
636 * go with the parent one.
641 /* First merge the hard-coded stuff. */
644 if (child->id != NOT_SET_P) merged->id = child->id;
645 if (child->rev != NOT_SET_P) merged->rev = child->rev;
646 if (child->msg != NOT_SET_P) merged->msg = child->msg;
647 if (child->logdata != NOT_SET_P) merged->logdata = child->logdata;
648 if (child->severity != NOT_SET) merged->severity = child->severity;
649 if (child->phase != NOT_SET) merged->phase = child->phase;
650 if (child->rule != NOT_SET_P) merged->rule = child->rule;
653 merged->is_chained = child->is_chained;
654 if (child->skip_count != NOT_SET) merged->skip_count = child->skip_count;
655 if (child->skip_after != NOT_SET_P) merged->skip_after = child->skip_after;
658 if (child->intercept_action != NOT_SET) {
659 merged->intercept_action_rec = child->intercept_action_rec;
660 merged->intercept_action = child->intercept_action;
661 merged->intercept_uri = child->intercept_uri;
664 if (child->intercept_status != NOT_SET) merged->intercept_status = child->intercept_status;
665 if (child->intercept_pause != NOT_SET) merged->intercept_pause = child->intercept_pause;
668 if (child->auditlog != NOT_SET) merged->auditlog = child->auditlog;
669 if (child->log != NOT_SET) merged->log = child->log;
672 /* Now merge the actions. */
674 tarr = apr_table_elts(child->actions);
675 telts = (const apr_table_entry_t*)tarr->elts;
676 for (i = 0; i < tarr->nelts; i++) {
677 msre_actionset_action_add(merged, (msre_action *)telts[i].val);
684 * Creates an actionset that contains a default list of actions.
686 msre_actionset *msre_actionset_create_default(msre_engine *engine) {
687 char *my_error_msg = NULL;
688 return msre_actionset_create(engine,
689 "phase:2,log,auditlog,pass",
694 * Sets the default values for the hard-coded actionset configuration.
696 void msre_actionset_set_defaults(msre_actionset *actionset) {
698 if (actionset->id == NOT_SET_P) actionset->id = NULL;
699 if (actionset->rev == NOT_SET_P) actionset->rev = NULL;
700 if (actionset->msg == NOT_SET_P) actionset->msg = NULL;
701 if (actionset->logdata == NOT_SET_P) actionset->logdata = NULL;
702 if (actionset->phase == NOT_SET) actionset->phase = 2;
703 if (actionset->severity == -1) {} /* leave at -1 */
704 if (actionset->rule == NOT_SET_P) actionset->rule = NULL;
707 if (actionset->is_chained == NOT_SET) actionset->is_chained = 0;
708 if (actionset->skip_count == NOT_SET) actionset->skip_count = 0;
709 if (actionset->skip_after == NOT_SET_P) actionset->skip_after = NULL;
712 if (actionset->parent_intercept_action_rec == NOT_SET_P) actionset->parent_intercept_action_rec = NULL;
713 if (actionset->intercept_action_rec == NOT_SET_P) actionset->intercept_action_rec = NULL;
714 if (actionset->parent_intercept_action == NOT_SET) actionset->parent_intercept_action = ACTION_NONE;
715 if (actionset->intercept_action == NOT_SET) actionset->intercept_action = ACTION_NONE;
716 if (actionset->intercept_uri == NOT_SET_P) actionset->intercept_uri = NULL;
717 if (actionset->intercept_status == NOT_SET) actionset->intercept_status = 403;
718 if (actionset->intercept_pause == NOT_SET) actionset->intercept_pause = 0;
721 if (actionset->auditlog == NOT_SET) actionset->auditlog = 1;
722 if (actionset->log == NOT_SET) actionset->log = 1;
725 /* -- Engine functions ----------------------------------------------------- */
728 * Creates a new engine instance.
730 msre_engine *msre_engine_create(apr_pool_t *parent_pool) {
734 /* Create new memory pool */
735 if (apr_pool_create(&mp, parent_pool) != APR_SUCCESS) return NULL;
738 engine = apr_pcalloc(mp, sizeof(msre_engine));
739 if (engine == NULL) return NULL;
741 engine->tfns = apr_table_make(mp, 25);
742 if (engine->tfns == NULL) return NULL;
743 engine->operators = apr_table_make(mp, 25);
744 if (engine->operators == NULL) return NULL;
745 engine->variables = apr_table_make(mp, 25);
746 if (engine->variables == NULL) return NULL;
747 engine->actions = apr_table_make(mp, 25);
748 if (engine->actions == NULL) return NULL;
754 * Destroys an engine instance, releasing the consumed memory.
756 void msre_engine_destroy(msre_engine *engine) {
757 /* Destroyed automatically by the parent pool.
758 * apr_pool_destroy(engine->mp);
763 /* -- Recipe functions ----------------------------------------------------- */
772 * Default implementation of the ruleset phase processing; it processes
773 * the rules in the ruleset attached to the currently active
776 #if defined(PERFORMANCE_MEASUREMENT)
778 #define PERFORMANCE_MEASUREMENT_LOOP 1000
780 static apr_status_t msre_ruleset_process_phase_(msre_ruleset *ruleset, modsec_rec *msr);
782 apr_status_t msre_ruleset_process_phase(msre_ruleset *ruleset, modsec_rec *msr) {
783 apr_array_header_t *arr = NULL;
784 msre_rule **rules = NULL;
788 switch (msr->phase) {
789 case PHASE_REQUEST_HEADERS :
790 arr = ruleset->phase_request_headers;
792 case PHASE_REQUEST_BODY :
793 arr = ruleset->phase_request_body;
795 case PHASE_RESPONSE_HEADERS :
796 arr = ruleset->phase_response_headers;
798 case PHASE_RESPONSE_BODY :
799 arr = ruleset->phase_response_body;
802 arr = ruleset->phase_logging;
805 msr_log(msr, 1, "Internal Error: Invalid phase %d", msr->phase);
809 rules = (msre_rule **)arr->elts;
810 for (i = 0; i < arr->nelts; i++) {
811 msre_rule *rule = rules[i];
812 rule->execution_time = 0;
815 for (i = 0; i < PERFORMANCE_MEASUREMENT_LOOP; i++) {
816 rc = msre_ruleset_process_phase_(ruleset, msr);
819 msr_log(msr, 1, "Phase %d", msr->phase);
821 rules = (msre_rule **)arr->elts;
822 for (i = 0; i < arr->nelts; i++) {
823 msre_rule *rule = rules[i];
825 /* Ignore markers, which are never processed. */
826 if (rule->placeholder == RULE_PH_MARKER) continue;
828 msr_log(msr, 1, "Rule %pp [id \"%s\"][file \"%s\"][line \"%d\"]: %u usec", rule,
829 ((rule->actionset != NULL)&&(rule->actionset->id != NULL)) ? rule->actionset->id : "-",
830 rule->filename != NULL ? rule->filename : "-",
832 (rule->execution_time / PERFORMANCE_MEASUREMENT_LOOP));
838 static apr_status_t msre_ruleset_process_phase_(msre_ruleset *ruleset, modsec_rec *msr) {
840 apr_status_t msre_ruleset_process_phase(msre_ruleset *ruleset, modsec_rec *msr) {
842 apr_array_header_t *arr = NULL;
845 const char *skip_after = NULL;
848 /* First determine which set of rules we need to use. */
849 switch (msr->phase) {
850 case PHASE_REQUEST_HEADERS :
851 arr = ruleset->phase_request_headers;
853 case PHASE_REQUEST_BODY :
854 arr = ruleset->phase_request_body;
856 case PHASE_RESPONSE_HEADERS :
857 arr = ruleset->phase_response_headers;
859 case PHASE_RESPONSE_BODY :
860 arr = ruleset->phase_response_body;
863 arr = ruleset->phase_logging;
866 msr_log(msr, 1, "Internal Error: Invalid phase %d", msr->phase);
870 if (msr->txcfg->debuglog_level >= 9) {
871 msr_log(msr, 9, "This phase consists of %d rule(s).", arr->nelts);
874 /* Loop through the rules in the selected set. */
877 rules = (msre_rule **)arr->elts;
878 for (i = 0; i < arr->nelts; i++) {
879 msre_rule *rule = rules[i];
880 #if defined(PERFORMANCE_MEASUREMENT)
881 apr_time_t time1 = 0;
884 /* Reset the rule interception flag */
885 msr->rule_was_intercepted = 0;
887 /* SKIP_RULES is used to skip all rules until we hit a placeholder
888 * with the specified rule ID and then resume execution after that.
890 if (mode == SKIP_RULES) {
891 /* Go to the next rule if we have not yet hit the skip_after ID */
892 if ((rule->placeholder == RULE_PH_NONE) || (rule->actionset->id == NULL) || (strcmp(skip_after, rule->actionset->id) != 0)) {
893 if (msr->txcfg->debuglog_level >= 9) {
894 if (rule->chain_starter != NULL) {
895 msr_log(msr, 9, "Skipping chain rule %pp id=\"%s\" until after id=\"%s\"", rule, (rule->chain_starter->actionset->id ? rule->chain_starter->actionset->id : "(none)"), skip_after);
899 msr_log(msr, 9, "Skipping rule %pp id=\"%s\" until after id=\"%s\"", rule, (rule->actionset->id ? rule->actionset->id : "(none)"), skip_after);
905 if (msr->txcfg->debuglog_level >= 9) {
906 msr_log(msr, 9, "Found rule %pp id=\"%s\".", rule, skip_after);
909 /* Go to the rule *after* this one to continue execution. */
910 if (msr->txcfg->debuglog_level >= 4) {
911 msr_log(msr, 4, "Continuing execution after rule id=\"%s\".", skip_after);
919 /* Skip any rule marked as a placeholder */
920 if (rule->placeholder != RULE_PH_NONE) {
924 /* NEXT_CHAIN is used when one of the rules in a chain
925 * fails to match and then we need to skip the remaining
926 * rules in that chain in order to get to the next
927 * rule that can execute.
929 if (mode == NEXT_CHAIN) {
930 if (rule->actionset->is_chained == 0) {
934 /* Go to the next rule. */
938 /* If we are here that means the mode is NEXT_RULE, which
939 * then means we have done processing any chains. However,
940 * if the "skip" parameter is set we need to skip over.
942 if ((mode == NEXT_RULE)&&(skip > 0)) {
943 /* Decrement the skip counter by one. */
946 /* If the current rule is part of a chain then
947 * we need to skip over the entire chain. Thus
948 * we change the mode to NEXT_CHAIN. The skip
949 * counter will not decrement as we are moving
950 * over the rules belonging to the chain.
952 if (rule->actionset->is_chained) {
956 /* Go to the next rule. */
960 /* Check if this rule was removed at runtime */
961 if ((rule->actionset->id !=NULL) && (! apr_is_empty_array(msr->removed_rules))) {
966 for(j = 0; j < msr->removed_rules->nelts; j++) {
967 range = ((const char**)msr->removed_rules->elts)[j];
969 if (msr->txcfg->debuglog_level >= 9) {
970 msr_log(msr, 9, "Checking removal of rule id=\"%s\" against: %s", rule->actionset->id, range);
973 if (rule_id_in_range(atoi(rule->actionset->id), range)) {
979 /* Go to the next rule if this one has been removed. */
980 if (do_process == 0) {
981 if (msr->txcfg->debuglog_level >= 5) {
982 msr_log(msr, 5, "Not processing %srule id=\"%s\": "
983 "removed by ctl action",
984 rule->actionset->is_chained ? "chained " : "",
985 rule->actionset->id);
988 /* Skip the whole chain, if this is a chained rule */
989 if (rule->actionset->is_chained) {
997 if (msr->txcfg->debuglog_level >= 4) {
998 apr_pool_t *p = msr->mp;
999 const char *fn = NULL;
1000 const char *id = NULL;
1001 const char *rev = NULL;
1003 if (rule->filename != NULL) {
1004 fn = apr_psprintf(p, " [file \"%s\"] [line \"%d\"]", rule->filename, rule->line_num);
1007 if (rule->actionset != NULL && rule->actionset->id != NULL) {
1008 id = apr_psprintf(p, " [id \"%s\"]", rule->actionset->id);
1011 if (rule->actionset != NULL && rule->actionset->rev != NULL) {
1012 rev = apr_psprintf(p, " [rev \"%s\"]", rule->actionset->rev);
1015 msr_log(msr, 4, "Recipe: Invoking rule %pp;%s%s%s.",
1016 rule, (fn ? fn : ""), (id ? id : ""), (rev ? rev : ""));
1017 msr_log(msr, 5, "Rule %pp: %s", rule, rule->unparsed);
1020 #if defined(PERFORMANCE_MEASUREMENT)
1021 time1 = apr_time_now();
1024 rc = msre_rule_process(rule, msr);
1026 #if defined(PERFORMANCE_MEASUREMENT)
1027 rule->execution_time += (apr_time_now() - time1);
1030 if (msr->txcfg->debuglog_level >= 4) {
1031 msr_log(msr, 4, "Rule returned %d.", rc);
1034 if (rc == RULE_NO_MATCH) {
1035 if (rule->actionset->is_chained) {
1036 /* If the current rule is part of a chain then
1037 * we need to skip over all the rules in the chain.
1040 if (msr->txcfg->debuglog_level >= 9) {
1041 msr_log(msr, 9, "No match, chained -> mode NEXT_CHAIN.");
1044 /* This rule is not part of a chain so we simply
1045 * move to the next rule.
1048 if (msr->txcfg->debuglog_level >= 9) {
1049 msr_log(msr, 9, "No match, not chained -> mode NEXT_RULE.");
1054 if (rc == RULE_MATCH) {
1055 if (msr->rule_was_intercepted) {
1056 /* If the transaction was intercepted by this rule we will
1057 * go back. Do note that we are relying on the
1058 * rule to know if it is a part of a chain and
1059 * not intercept if it is.
1061 if (msr->txcfg->debuglog_level >= 9) {
1062 msr_log(msr, 9, "Match, intercepted -> returning.");
1067 if (rule->actionset->skip_after != NULL) {
1068 skip_after = rule->actionset->skip_after;
1071 if (msr->txcfg->debuglog_level >= 9) {
1072 msr_log(msr, 9, "Skipping after rule %pp id=\"%s\" -> mode SKIP_RULES.", rule, skip_after);
1078 /* We had a match but the transaction was not
1079 * intercepted. In that case we proceed with the
1083 if (msr->txcfg->debuglog_level >= 9) {
1084 msr_log(msr, 9, "Match -> mode NEXT_RULE.");
1087 /* ...unless we need to skip, in which case we
1088 * determine how many rules/chains we need to
1089 * skip and configure the counter accordingly.
1091 if (rule->actionset->is_chained == 0) {
1092 if (rule->chain_starter != NULL) {
1093 if (rule->chain_starter->actionset->skip_count > 0) {
1094 skip = rule->chain_starter->actionset->skip_count;
1095 if (msr->txcfg->debuglog_level >= 4) {
1096 msr_log(msr, 4, "Skipping %d rules/chains (from a chain).", skip);
1100 else if (rule->actionset->skip_count > 0) {
1101 skip = rule->actionset->skip_count;
1102 if (msr->txcfg->debuglog_level >= 4) {
1103 msr_log(msr, 4, "Skipping %d rules/chains.", skip);
1109 msr_log(msr, 1, "Rule processing failed.");
1113 msr_log(msr, 1, "Rule processing failed with unknown return code: %d.", rc);
1118 /* ENH warn if chained rules are missing. */
1124 * Creates a ruleset that will be handled by the default
1127 msre_ruleset *msre_ruleset_create(msre_engine *engine, apr_pool_t *mp) {
1128 msre_ruleset *ruleset;
1130 ruleset = apr_pcalloc(mp, sizeof(msre_ruleset));
1131 if (ruleset == NULL) return NULL;
1133 ruleset->engine = engine;
1135 ruleset->phase_request_headers = apr_array_make(ruleset->mp, 25, sizeof(const msre_rule *));
1136 ruleset->phase_request_body = apr_array_make(ruleset->mp, 25, sizeof(const msre_rule *));
1137 ruleset->phase_response_headers = apr_array_make(ruleset->mp, 25, sizeof(const msre_rule *));
1138 ruleset->phase_response_body = apr_array_make(ruleset->mp, 25, sizeof(const msre_rule *));
1139 ruleset->phase_logging = apr_array_make(ruleset->mp, 25, sizeof(const msre_rule *));
1145 * Adds one rule to the given phase of the ruleset.
1147 int msre_ruleset_rule_add(msre_ruleset *ruleset, msre_rule *rule, int phase) {
1148 apr_array_header_t *arr = NULL;
1151 case PHASE_REQUEST_HEADERS :
1152 arr = ruleset->phase_request_headers;
1154 case PHASE_REQUEST_BODY :
1155 arr = ruleset->phase_request_body;
1157 case PHASE_RESPONSE_HEADERS :
1158 arr = ruleset->phase_response_headers;
1160 case PHASE_RESPONSE_BODY :
1161 arr = ruleset->phase_response_body;
1163 case PHASE_LOGGING :
1164 arr = ruleset->phase_logging;
1170 /* ENH verify the rule's use of targets is consistent with
1171 * the phase it selected to run at.
1174 msre_actionset_set_defaults(rule->actionset);
1175 rule->actionset->rule = rule;
1177 *(const msre_rule **)apr_array_push(arr) = rule;
1182 static msre_rule * msre_ruleset_fetch_phase_rule(const msre_ruleset *ruleset, const char *id,
1183 const apr_array_header_t *phase_arr)
1185 msre_rule **rules = (msre_rule **)phase_arr->elts;
1188 for (i = 0; i < phase_arr->nelts; i++) {
1189 msre_rule *rule = (msre_rule *)rules[i];
1191 /* Rule with an action, not a sub-rule (chain) and a matching id */
1192 if ( (rule->actionset != NULL)
1193 && (!rule->actionset->is_chained || !rule->chain_starter)
1194 && (rule->actionset->id != NULL)
1195 && (strcmp(rule->actionset->id, id) == 0))
1197 /* Return rule that matched unless it is a placeholder */
1198 return (rule->placeholder == RULE_PH_NONE) ? rule : NULL;
1206 * Fetches rule from the ruleset all rules that match the given exception.
1208 msre_rule * msre_ruleset_fetch_rule(msre_ruleset *ruleset, const char *id) {
1209 msre_rule *rule = NULL;
1211 if (ruleset == NULL) return NULL;
1213 rule = msre_ruleset_fetch_phase_rule(ruleset, id, ruleset->phase_request_headers);
1214 if (rule != NULL) return rule;
1216 rule = msre_ruleset_fetch_phase_rule(ruleset, id, ruleset->phase_request_body);
1217 if (rule != NULL) return rule;
1219 rule = msre_ruleset_fetch_phase_rule(ruleset, id, ruleset->phase_response_headers);
1220 if (rule != NULL) return rule;
1222 rule = msre_ruleset_fetch_phase_rule(ruleset, id, ruleset->phase_response_body);
1223 if (rule != NULL) return rule;
1225 rule = msre_ruleset_fetch_phase_rule(ruleset, id, ruleset->phase_logging);
1230 static int msre_ruleset_phase_rule_remove_with_exception(msre_ruleset *ruleset, rule_exception *re,
1231 apr_array_header_t *phase_arr)
1234 int i, j, mode, removed_count;
1239 rules = (msre_rule **)phase_arr->elts;
1240 for (i = 0; i < phase_arr->nelts; i++) {
1241 msre_rule *rule = (msre_rule *)rules[i];
1243 if (mode == 0) { /* Looking for next rule. */
1244 int remove_rule = 0;
1246 /* Only remove non-placeholder rules */
1247 if (rule->placeholder == RULE_PH_NONE) {
1249 case RULE_EXCEPTION_REMOVE_ID :
1250 if ((rule->actionset != NULL)&&(rule->actionset->id != NULL)) {
1251 int ruleid = atoi(rule->actionset->id);
1253 if (rule_id_in_range(ruleid, re->param)) {
1260 case RULE_EXCEPTION_REMOVE_MSG :
1261 if ((rule->actionset != NULL)&&(rule->actionset->msg != NULL)) {
1262 char *my_error_msg = NULL;
1264 int rc = msc_regexec(re->param_data,
1265 rule->actionset->msg, strlen(rule->actionset->msg),
1277 /* Do not increment j. */
1279 if (rule->actionset->is_chained) mode = 2; /* Remove rules in this chain. */
1281 if (rule->actionset->is_chained) mode = 1; /* Keep rules in this chain. */
1282 rules[j++] = rules[i];
1284 } else { /* Handling rule that is part of a chain. */
1285 if (mode == 2) { /* We want to remove the rule. */
1286 /* Do not increment j. */
1289 rules[j++] = rules[i];
1292 if ((rule->actionset == NULL)||(rule->actionset->is_chained == 0)) mode = 0;
1296 /* Update the number of rules in the array. */
1297 phase_arr->nelts -= removed_count;
1303 * Removes from the ruleset all rules that match the given exception.
1305 int msre_ruleset_rule_remove_with_exception(msre_ruleset *ruleset, rule_exception *re) {
1308 if (ruleset == NULL) return 0;
1310 count += msre_ruleset_phase_rule_remove_with_exception(ruleset, re, ruleset->phase_request_headers);
1311 count += msre_ruleset_phase_rule_remove_with_exception(ruleset, re, ruleset->phase_request_body);
1312 count += msre_ruleset_phase_rule_remove_with_exception(ruleset, re, ruleset->phase_response_headers);
1313 count += msre_ruleset_phase_rule_remove_with_exception(ruleset, re, ruleset->phase_response_body);
1314 count += msre_ruleset_phase_rule_remove_with_exception(ruleset, re, ruleset->phase_logging);
1320 /* -- Rule functions ------------------------------------------------------- */
1323 * Returns the name of the supplied severity level.
1325 static const char *msre_format_severity(int severity) {
1326 if ((severity >= 0)&&(severity <= 7)) {
1327 return severities[severity];
1330 return "(invalid value)";
1335 * Creates a string containing the metadata of the supplied rule.
1337 char *msre_format_metadata(modsec_rec *msr, msre_actionset *actionset) {
1338 const apr_array_header_t *tarr;
1339 const apr_table_entry_t *telts;
1344 char *severity = "";
1349 if (actionset == NULL) return "";
1351 if ((actionset->rule != NULL) && (actionset->rule->filename != NULL)) {
1352 fn = apr_psprintf(msr->mp, " [file \"%s\"] [line \"%d\"]",
1353 actionset->rule->filename, actionset->rule->line_num);
1355 if (actionset->id != NULL) {
1356 id = apr_psprintf(msr->mp, " [id \"%s\"]",
1357 log_escape(msr->mp, actionset->id));
1359 if (actionset->rev != NULL) {
1360 rev = apr_psprintf(msr->mp, " [rev \"%s\"]",
1361 log_escape(msr->mp, actionset->rev));
1363 if (actionset->msg != NULL) {
1364 /* Expand variables in the message string. */
1365 msc_string *var = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
1366 var->value = (char *)actionset->msg;
1367 var->value_len = strlen(actionset->msg);
1368 expand_macros(msr, var, NULL, msr->mp);
1370 msg = apr_psprintf(msr->mp, " [msg \"%s\"]",
1371 log_escape_ex(msr->mp, var->value, var->value_len));
1373 if (actionset->logdata != NULL) {
1374 /* Expand variables in the message string. */
1375 msc_string *var = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
1376 var->value = (char *)actionset->logdata;
1377 var->value_len = strlen(actionset->logdata);
1378 expand_macros(msr, var, NULL, msr->mp);
1380 logdata = apr_psprintf(msr->mp, " [data \"%s\"]",
1381 log_escape_hex(msr->mp, (unsigned char *)var->value, var->value_len));
1383 /* If it is > 512 bytes, then truncate at 512 with ellipsis.
1384 * NOTE: 512 actual data + 9 bytes of label = 521
1386 if (strlen(logdata) > 521) {
1392 logdata[522] = '\0';
1395 if ((actionset->severity >= 0)&&(actionset->severity <= 7)) {
1396 severity = apr_psprintf(msr->mp, " [severity \"%s\"]",
1397 msre_format_severity(actionset->severity));
1400 /* Extract rule tags from the action list. */
1401 tarr = apr_table_elts(actionset->actions);
1402 telts = (const apr_table_entry_t*)tarr->elts;
1404 for (k = 0; k < tarr->nelts; k++) {
1405 msre_action *action = (msre_action *)telts[k].val;
1406 if (strcmp(telts[k].key, "tag") == 0) {
1407 tags = apr_psprintf(msr->mp, "%s [tag \"%s\"]", tags,
1408 log_escape(msr->mp, action->param));
1412 return apr_pstrcat(msr->mp, fn, id, rev, msg, logdata, severity, tags, NULL);
1415 char * msre_rule_generate_unparsed(apr_pool_t *pool, const msre_rule *rule, const char *targets,
1416 const char *args, const char *actions)
1418 char *unparsed = NULL;
1419 const char *r_targets = targets;
1420 const char *r_args = args;
1421 const char *r_actions = actions;
1423 if (r_targets == NULL) {
1424 r_targets = rule->p1;
1426 if (r_args == NULL) {
1427 r_args = apr_pstrcat(pool, (rule->op_negated ? "!" : ""), "@", rule->op_name, " ", rule->op_param, NULL);
1429 if (r_actions == NULL) {
1430 r_actions = msre_actionset_generate_action_string(pool, rule->actionset);
1433 switch (rule->type) {
1434 case RULE_TYPE_NORMAL:
1435 if (r_actions == NULL) {
1436 unparsed = apr_psprintf(pool, "SecRule \"%s\" \"%s\"",
1437 log_escape(pool, r_targets), log_escape(pool, r_args));
1440 unparsed = apr_psprintf(pool, "SecRule \"%s\" \"%s\" \"%s\"",
1441 log_escape(pool, r_targets), log_escape(pool, r_args),
1442 log_escape(pool, r_actions));
1445 case RULE_TYPE_ACTION:
1446 unparsed = apr_psprintf(pool, "SecAction \"%s\"",
1447 log_escape(pool, r_actions));
1449 case RULE_TYPE_MARKER:
1450 unparsed = apr_psprintf(pool, "SecMarker \"%s\"", rule->actionset->id);
1452 #if defined(WITH_LUA)
1455 if (r_actions == NULL) {
1456 unparsed = apr_psprintf(pool, "SecRuleScript \"%s\"", r_args);
1459 unparsed = apr_psprintf(pool, "SecRuleScript \"%s\" \"%s\"",
1460 r_args, log_escape(pool, r_actions));
1470 * Assembles a new rule using the strings that contain a list
1471 * of targets (variables), arguments, and actions.
1473 msre_rule *msre_rule_create(msre_ruleset *ruleset, int type,
1474 const char *fn, int line, const char *targets,
1475 const char *args, const char *actions, char **error_msg)
1482 if (error_msg == NULL) return NULL;
1485 rule = (msre_rule *)apr_pcalloc(ruleset->mp, sizeof(msre_rule));
1486 if (rule == NULL) return NULL;
1489 rule->ruleset = ruleset;
1490 rule->targets = apr_array_make(ruleset->mp, 10, sizeof(const msre_var *));
1491 rule->p1 = apr_pstrdup(ruleset->mp, targets);
1492 rule->filename = apr_pstrdup(ruleset->mp, fn);
1493 rule->line_num = line;
1496 rc = msre_parse_targets(ruleset, targets, rule->targets, &my_error_msg);
1498 *error_msg = apr_psprintf(ruleset->mp, "Error creating rule: %s", my_error_msg);
1505 /* Is negation used? */
1506 if (*argsp == '!') {
1507 rule->op_negated = 1;
1509 while((isspace(*argsp))&&(*argsp != '\0')) argsp++;
1512 /* Is the operator explicitly selected? */
1513 if (*argsp != '@') {
1514 /* Go with a regular expression. */
1515 rule->op_name = "rx";
1516 rule->op_param = argsp;
1518 /* Explicitly selected operator. */
1519 char *p = (char *)(argsp + 1);
1520 while((!isspace(*p))&&(*p != '\0')) p++;
1521 rule->op_name = apr_pstrmemdup(ruleset->mp, argsp + 1, p - (argsp + 1));
1522 while(isspace(*p)) p++; /* skip over the whitespace at the end*/
1523 rule->op_param = p; /* IMP1 So we always have a parameter even when it's empty? */
1526 /* Find the operator. */
1527 rule->op_metadata = msre_engine_op_resolve(ruleset->engine, rule->op_name);
1528 if (rule->op_metadata == NULL) {
1529 *error_msg = apr_psprintf(ruleset->mp,
1530 "Error creating rule: Failed to resolve operator: %s", rule->op_name);
1534 /* Initialise & validate parameter */
1535 if (rule->op_metadata->param_init != NULL) {
1536 if (rule->op_metadata->param_init(rule, &my_error_msg) <= 0) {
1537 *error_msg = apr_psprintf(ruleset->mp, "Error creating rule: %s", my_error_msg);
1543 if (actions != NULL) {
1544 /* Create per-rule actionset */
1545 rule->actionset = msre_actionset_create(ruleset->engine, actions, &my_error_msg);
1546 if (rule->actionset == NULL) {
1547 *error_msg = apr_psprintf(ruleset->mp, "Error parsing actions: %s", my_error_msg);
1552 /* Add the unparsed rule */
1553 rule->unparsed = msre_rule_generate_unparsed(ruleset->mp, rule, targets, args, NULL);
1558 #if defined(WITH_LUA)
1562 msre_rule *msre_rule_lua_create(msre_ruleset *ruleset,
1563 const char *fn, int line, const char *script_filename,
1564 const char *actions, char **error_msg)
1569 if (error_msg == NULL) return NULL;
1572 rule = (msre_rule *)apr_pcalloc(ruleset->mp, sizeof(msre_rule));
1573 if (rule == NULL) return NULL;
1575 rule->type = RULE_TYPE_LUA;
1576 rule->ruleset = ruleset;
1577 rule->filename = apr_pstrdup(ruleset->mp, fn);
1578 rule->line_num = line;
1580 /* Compile script. */
1581 *error_msg = lua_compile(&rule->script, script_filename, ruleset->mp);
1582 if (*error_msg != NULL) {
1587 if (actions != NULL) {
1588 /* Create per-rule actionset */
1589 rule->actionset = msre_actionset_create(ruleset->engine, actions, &my_error_msg);
1590 if (rule->actionset == NULL) {
1591 *error_msg = apr_psprintf(ruleset->mp, "Error parsing actions: %s", my_error_msg);
1596 /* Add the unparsed rule */
1597 rule->unparsed = msre_rule_generate_unparsed(ruleset->mp, rule, NULL, script_filename, NULL);
1604 * Perform non-disruptive actions associated with the provided actionset.
1606 static void msre_perform_nondisruptive_actions(modsec_rec *msr, msre_rule *rule,
1607 msre_actionset *actionset, apr_pool_t *mptmp)
1609 const apr_array_header_t *tarr;
1610 const apr_table_entry_t *telts;
1613 tarr = apr_table_elts(actionset->actions);
1614 telts = (const apr_table_entry_t*)tarr->elts;
1615 for (i = 0; i < tarr->nelts; i++) {
1616 msre_action *action = (msre_action *)telts[i].val;
1617 if (action->metadata->type == ACTION_NON_DISRUPTIVE) {
1618 if (action->metadata->execute != NULL) {
1619 action->metadata->execute(msr, mptmp, rule, action);
1626 * Perform the disruptive actions associated with the given actionset.
1628 static void msre_perform_disruptive_actions(modsec_rec *msr, msre_rule *rule,
1629 msre_actionset *actionset, apr_pool_t *mptmp, const char *message)
1631 const apr_array_header_t *tarr;
1632 const apr_table_entry_t *telts;
1635 /* Execute the disruptive actions. Do note that this does
1636 * not mean the request will be interrupted straight away. All
1637 * disruptive actions need to do here is update the information
1638 * that will be used to act later.
1640 tarr = apr_table_elts(actionset->actions);
1641 telts = (const apr_table_entry_t*)tarr->elts;
1642 for (i = 0; i < tarr->nelts; i++) {
1643 msre_action *action = (msre_action *)telts[i].val;
1644 if (action->metadata->type == ACTION_DISRUPTIVE) {
1645 if (action->metadata->execute != NULL) {
1646 action->metadata->execute(msr, mptmp, rule, action);
1651 /* If "noauditlog" was used do not mark the transaction relevant. */
1652 if (actionset->auditlog != 0) {
1656 /* We only do stuff when in ONLINE mode. In all other
1657 * cases we only emit warnings.
1659 if ((msr->phase == PHASE_LOGGING)
1660 || (msr->txcfg->is_enabled == MODSEC_DETECTION_ONLY)
1661 || (msr->modsecurity->processing_mode == MODSEC_OFFLINE)
1662 || (actionset->intercept_action == ACTION_NONE))
1666 /* If "nolog" was used log at a higher level to prevent an "alert". */
1667 if (actionset->log == 0) {
1670 /* But, if "auditlog" is enabled, then still add the message. */
1671 if (actionset->auditlog != 0) {
1672 *(const char **)apr_array_push(msr->alerts) = msc_alert_message(msr, actionset, NULL, message);
1680 msc_alert(msr, log_level, actionset, "Warning.", message);
1682 /* However, this will mark the txn relevant again if it is <= 3,
1683 * which will mess up noauditlog. We need to compensate for this
1684 * so that we do not increment twice when auditlog is enabled and
1685 * prevent incrementing when auditlog is disabled.
1687 if (log_level <= 3) {
1694 /* Signal to the engine we need to intercept this
1695 * transaction, and rememer the rule that caused it.
1697 msr->was_intercepted = 1;
1698 msr->rule_was_intercepted = 1;
1699 msr->intercept_phase = msr->phase;
1700 msr->intercept_actionset = actionset;
1701 msr->intercept_message = message;
1705 * Invokes the rule operator against the given value.
1707 static int execute_operator(msre_var *var, msre_rule *rule, modsec_rec *msr,
1708 msre_actionset *acting_actionset, apr_pool_t *mptmp)
1710 apr_time_t time_before_op = 0;
1711 char *my_error_msg = NULL;
1712 const char *full_varname = NULL;
1715 /* determine the full var name if not already resolved
1717 * NOTE: this can happen if the var does not match but it is
1718 * being tested for non-existance as in:
1719 * @REQUEST_HEADERS:Foo "@eq 0"
1720 * @REQUEST_HEADERS:Foo "!@eq 1"
1722 if ((var->param != NULL) && (var->name != NULL) && (strchr(var->name,':') == NULL)) {
1723 full_varname = apr_psprintf(mptmp, "%s%s:%s",
1724 (var->is_counting ? "&" : ""),
1725 var->name, var->param);
1727 else if ((var->name != NULL) && var->is_counting && (*var->name != '&')) {
1728 full_varname = apr_pstrcat(mptmp, "&", var->name, NULL);
1731 full_varname = var->name;
1734 if (msr->txcfg->debuglog_level >= 4) {
1735 msr_log(msr, 4, "Executing operator \"%s%s\" with param \"%s\" against %s.",
1736 (rule->op_negated ? "!" : ""), rule->op_name,
1737 log_escape(msr->mp, rule->op_param), full_varname);
1740 if (msr->txcfg->debuglog_level >= 9) {
1741 msr_log(msr, 9, "Target value: \"%s\"", log_escape_nq_ex(msr->mp, var->value,
1745 #if defined(PERFORMANCE_MEASUREMENT)
1746 time_before_op = apr_time_now();
1748 if (msr->txcfg->debuglog_level >= 4) {
1749 time_before_op = apr_time_now();
1753 rc = rule->op_metadata->execute(msr, rule, var, &my_error_msg);
1755 #if defined(PERFORMANCE_MEASUREMENT)
1757 /* Record performance but do not log anything. */
1758 apr_time_t t1 = apr_time_now();
1759 rule->op_time += (t1 - time_before_op);
1762 if (msr->txcfg->debuglog_level >= 4) {
1763 apr_time_t t1 = apr_time_now();
1764 msr_log(msr, 4, "Operator completed in %" APR_TIME_T_FMT " usec.", (t1 - time_before_op));
1769 msr_log(msr, 4, "Operator error: %s", my_error_msg);
1773 if (((rc == 0)&&(rule->op_negated == 0))||((rc == 1)&&(rule->op_negated == 1))) {
1774 /* No match, do nothing. */
1775 return RULE_NO_MATCH;
1780 char *op_param = log_escape(msr->mp, rule->op_param);
1782 /* Truncate op parameter. */
1783 if (strlen(op_param) > 252) {
1784 op_param = apr_psprintf(msr->mp, "%.252s ...", op_param);
1787 /* Operator did not match so we need to provide a message. */
1788 my_error_msg = apr_psprintf(msr->mp, "Match of \"%s %s\" against \"%s\" required.",
1789 log_escape(msr->mp, rule->op_name), op_param,
1790 log_escape(msr->mp, full_varname));
1793 /* Save the rules that match */
1794 *(const msre_rule **)apr_array_push(msr->matched_rules) = rule;
1796 /* Save the last matched var data */
1797 msr->matched_var->name = apr_pstrdup(msr->mp, var->name);
1798 msr->matched_var->name_len = strlen(msr->matched_var->name);
1799 msr->matched_var->value = apr_pmemdup(msr->mp, var->value, var->value_len);
1800 msr->matched_var->value_len = var->value_len;
1802 /* Keep track of the highest severity matched so far */
1803 if ((acting_actionset->severity > 0) && (acting_actionset->severity < msr->highest_severity))
1805 msr->highest_severity = acting_actionset->severity;
1809 /* Perform non-disruptive actions. */
1810 msre_perform_nondisruptive_actions(msr, rule, rule->actionset, mptmp);
1812 /* Perform disruptive actions, but only if
1813 * this rule is not part of a chain.
1815 if (rule->actionset->is_chained == 0) {
1816 msre_perform_disruptive_actions(msr, rule, acting_actionset, mptmp, my_error_msg);
1824 * Executes rule against the given transaction.
1826 static apr_status_t msre_rule_process_normal(msre_rule *rule, modsec_rec *msr) {
1827 const apr_array_header_t *arr = NULL;
1828 const apr_table_entry_t *te = NULL;
1829 msre_actionset *acting_actionset = NULL;
1830 msre_var **targets = NULL;
1831 apr_pool_t *mptmp = msr->msc_rule_mptmp;
1832 apr_table_t *tartab = NULL;
1833 apr_table_t *vartab = NULL;
1834 int i, rc = 0, match_count = 0;
1835 int invocations = 0;
1836 int multi_match = 0;
1838 /* Choose the correct metadata/disruptive action actionset. */
1839 acting_actionset = rule->actionset;
1840 if (rule->chain_starter != NULL) {
1841 acting_actionset = rule->chain_starter->actionset;
1844 /* Configure recursive matching. */
1845 if (apr_table_get(rule->actionset->actions, "multiMatch") != NULL) {
1849 /* ENH: What is a good initial size? */
1850 tartab = apr_table_make(mptmp, 24);
1851 if (tartab == NULL) return -1;
1852 vartab = apr_table_make(mptmp, 24);
1853 if (vartab == NULL) return -1;
1855 /* Expand variables to create a list of targets. */
1857 targets = (msre_var **)rule->targets->elts;
1858 for (i = 0; i < rule->targets->nelts; i++) {
1861 apr_table_clear(vartab);
1863 /* ENH Introduce a new variable hook that would allow the code
1864 * behind the variable to return the size of the collection
1865 * without having to generate the variables.
1868 /* Expand individual variables first. */
1869 list_count = targets[i]->metadata->generate(msr, targets[i], rule, vartab, mptmp);
1871 if (targets[i]->is_counting) {
1872 /* Count how many there are and just add the score to the target list. */
1873 msre_var *newvar = (msre_var *)apr_pmemdup(mptmp, targets[i], sizeof(msre_var));
1874 newvar->value = apr_psprintf(mptmp, "%d", list_count);
1875 newvar->value_len = strlen(newvar->value);
1876 apr_table_addn(tartab, newvar->name, (void *)newvar);
1878 /* And either add them or remove from the final target list. */
1879 arr = apr_table_elts(vartab);
1880 te = (apr_table_entry_t *)arr->elts;
1881 for(j = 0; j < arr->nelts; j++) {
1882 if (targets[i]->is_negated == 0) {
1883 apr_table_addn(tartab, te[j].key, te[j].val);
1885 apr_table_unset(tartab, te[j].key);
1891 /* Log the target variable expansion */
1892 if (msr->txcfg->debuglog_level >= 4) {
1893 const char *expnames = NULL;
1895 arr = apr_table_elts(tartab);
1896 if (arr->nelts > 1) {
1897 te = (apr_table_entry_t *)arr->elts;
1898 expnames = apr_pstrdup(mptmp, ((msre_var *)te[0].val)->name);
1899 for(i = 1; i < arr->nelts; i++) {
1900 expnames = apr_psprintf(mptmp, "%s|%s", expnames, ((msre_var *)te[i].val)->name);
1902 if (strcmp(rule->p1, expnames) != 0) {
1903 msr_log(msr, 4, "Expanded \"%s\" to \"%s\".", rule->p1, expnames);
1908 /* Loop through targets on the final target list,
1909 * perform transformations as necessary, and invoke
1913 arr = apr_table_elts(tartab);
1914 te = (apr_table_entry_t *)arr->elts;
1915 for (i = 0; i < arr->nelts; i++) {
1918 apr_table_t *cachetab = NULL;
1919 apr_time_t time_before_trans = 0;
1922 /* Take one target. */
1923 var = (msre_var *)te[i].val;
1925 /* Is this var cacheable? */
1926 if (msr->txcfg->cache_trans != MODSEC_CACHE_DISABLED) {
1929 /* Counting vars are not cacheable due to them being created
1930 * in a local per-rule pool.
1932 if (var->is_counting) {
1933 if (msr->txcfg->debuglog_level >= 9) {
1934 msr_log(msr, 9, "CACHE: Disabled - &%s is dynamic", var->name);
1939 /* Only cache if if the variable is available in this phase */
1940 else if (msr->phase < var->metadata->availability) {
1941 if (msr->txcfg->debuglog_level >= 9) {
1942 msr_log(msr, 9, "CACHE: Disabled - %s is not yet available in phase %d (requires phase %d or later)", var->name, msr->phase, var->metadata->availability);
1947 /* check the cache options */
1948 else if (var->value_len < msr->txcfg->cache_trans_min) {
1949 if (msr->txcfg->debuglog_level >= 9) {
1950 msr_log(msr, 9, "CACHE: Disabled - %s value length=%u, smaller than minlen=%" APR_SIZE_T_FMT, var->name, var->value_len, msr->txcfg->cache_trans_min);
1955 else if ((msr->txcfg->cache_trans_max != 0) && (var->value_len > msr->txcfg->cache_trans_max)) {
1956 if (msr->txcfg->debuglog_level >= 9) {
1957 msr_log(msr, 9, "CACHE: Disabled - %s value length=%u, larger than maxlen=%" APR_SIZE_T_FMT, var->name, var->value_len, msr->txcfg->cache_trans_max);
1963 /* if cache is still enabled, check the VAR for cacheablity */
1965 if (var->metadata->is_cacheable == VAR_CACHE) {
1966 if (msr->txcfg->debuglog_level >= 9) {
1967 msr_log(msr, 9, "CACHE: Enabled");
1971 msr_log(msr, 9, "CACHE: Fetching cache entry from hash=%pp: %pp=%s", msr->tcache, var, var->name);
1974 /* Fetch cache table for this target */
1975 cachetab = (apr_table_t *)apr_hash_get(msr->tcache, var->value, sizeof(var->value));
1977 /* Create an empty cache table if this is the first time */
1980 msr_log(msr, 9, "CACHE: Using cache table %pp", cachetab);
1984 if (cachetab == NULL)
1987 /* NOTE: We use the pointer to the var value as a hash
1988 * key as it is unique. This pointer *must*
1989 * remain valid through the entire phase. If
1990 * it does not, then we will not receive a cache
1991 * hit and just wasted RAM. So, it is important
1992 * that any such vars be marked as VAR_DONT_CACHE.
1994 * ENH: Only use pointer for non-scalar vars
1996 cachetab = apr_table_make(msr->mp, 3);
1997 apr_hash_set(msr->tcache, var->value, sizeof(var->value), cachetab);
2000 msr_log(msr, 9, "CACHE: Created a new cache table %pp for %pp", cachetab, var->value);
2008 if (msr->txcfg->debuglog_level >= 9) {
2009 msr_log(msr, 9, "CACHE: %s transformations are not cacheable", var->name);
2015 #if defined(PERFORMANCE_MEASUREMENT)
2016 time_before_trans = apr_time_now();
2018 if (msr->txcfg->debuglog_level >= 4) {
2019 time_before_trans = apr_time_now();
2023 /* Transform target. */
2025 const apr_array_header_t *tarr;
2026 const apr_table_entry_t *telts;
2027 const char *tfnspath = NULL;
2028 char *tfnskey = NULL;
2030 int last_cached_tfn = 0;
2031 msre_cache_rec *crec = NULL;
2032 msre_cache_rec *last_crec = NULL;
2034 msre_action *action;
2035 msre_tfn_metadata *metadata;
2036 apr_table_t *normtab;
2037 const char *lastvarval = NULL;
2038 apr_size_t lastvarlen = 0;
2041 normtab = apr_table_make(mptmp, 10);
2042 if (normtab == NULL) return -1;
2043 tarr = apr_table_elts(rule->actionset->actions);
2044 telts = (const apr_table_entry_t*)tarr->elts;
2046 /* Build the final list of transformation functions. */
2047 for (k = 0; k < tarr->nelts; k++) {
2048 action = (msre_action *)telts[k].val;
2050 if (strcmp(telts[k].key, "t") == 0) {
2051 if (strcmp(action->param, "none") == 0) {
2052 apr_table_clear(normtab);
2057 last_cached_tfn = 0;
2061 if (action->param_plusminus == NEGATIVE_VALUE) {
2062 apr_table_unset(normtab, action->param);
2067 apr_table_addn(normtab, action->param, (void *)action);
2069 /* Check the cache, saving the 'most complete' as a
2073 tfnspath = apr_psprintf(mptmp, "%s%s%s", (tfnspath?tfnspath:""), (tfnspath?",":""), action->param);
2074 tfnskey = apr_psprintf(mptmp, "%x;%s", tfnscount, tfnspath);
2075 crec = (msre_cache_rec *)apr_table_get(cachetab, tfnskey);
2078 msr_log(msr, 9, "CACHE: %s %s cached=%d", var->name, tfnskey, (crec ? 1 : 0));
2083 last_cached_tfn = tfnscount;
2090 /* If the last cached tfn is the last in the list
2091 * then we can stop here and just execute the action immediatly
2093 if (usecache && !multi_match &&
2094 (crec != NULL) && (crec == last_crec))
2098 if (crec->changed) {
2099 var->value = apr_pmemdup(mptmp, crec->val, crec->val_len);
2100 var->value_len = crec->val_len;
2103 if (msr->txcfg->debuglog_level >= 9) {
2104 msr_log(msr, 9, "T (%d) %s: \"%s\" [fully cached hits=%d]", crec->changed, crec->path,
2105 log_escape_nq_ex(mptmp, var->value, var->value_len), crec->hits);
2108 #if defined(PERFORMANCE_MEASUREMENT)
2110 apr_time_t t1 = apr_time_now();
2111 rule->trans_time += (t1 - time_before_trans);
2114 if (msr->txcfg->debuglog_level >= 4) {
2115 apr_time_t t1 = apr_time_now();
2117 msr_log(msr, 4, "Transformation completed in %" APR_TIME_T_FMT " usec.",
2118 (t1 - time_before_trans));
2122 rc = execute_operator(var, rule, msr, acting_actionset, mptmp);
2128 if (rc == RULE_MATCH) {
2131 /* Return straight away if the transaction
2132 * was intercepted - no need to process the remaining
2135 if (msr->rule_was_intercepted) {
2140 continue; /* next target */
2144 /* Perform transformations. */
2146 tarr = apr_table_elts(normtab);
2148 /* Execute transformations in a loop. */
2150 /* Start after the last known cached transformation if we can */
2151 if (!multi_match && (last_crec != NULL)) {
2152 k = last_cached_tfn;
2153 tfnspath = last_crec->path;
2156 if ((changed = last_crec->changed) > 0) {
2157 var->value = last_crec->val;
2158 var->value_len = last_crec->val_len;
2161 if (msr->txcfg->debuglog_level >= 9) {
2162 msr_log(msr, 9, "T (%d) %s: \"%s\" [partially cached hits=%d]", last_crec->changed,
2163 tfnspath, log_escape_nq_ex(mptmp, var->value, var->value_len), last_crec->hits);
2171 /* Make a copy of the value so that we can change it in-place. */
2173 var->value = apr_pstrmemdup(mptmp, var->value, var->value_len);
2174 /* var->value_len remains the same */
2177 telts = (const apr_table_entry_t*)tarr->elts;
2178 for (; k < tarr->nelts; k++) {
2180 long int rval_length = -1;
2183 /* In multi-match mode we execute the operator
2184 * once at the beginning and then once every
2185 * time the variable is changed by the transformation
2188 if (multi_match && (k == 0 || tfnchanged)) {
2191 #if defined(PERFORMANCE_MEASUREMENT)
2193 apr_time_t t1 = apr_time_now();
2194 rule->trans_time += (t1 - time_before_trans);
2197 if (msr->txcfg->debuglog_level >= 4) {
2198 apr_time_t t1 = apr_time_now();
2200 msr_log(msr, 4, "Transformation completed in %" APR_TIME_T_FMT " usec.",
2201 (t1 - time_before_trans));
2205 rc = execute_operator(var, rule, msr, acting_actionset, mptmp);
2211 if (rc == RULE_MATCH) {
2214 /* Return straight away if the transaction
2215 * was intercepted - no need to process the remaining
2218 if (msr->rule_was_intercepted) {
2224 /* Perform one transformation. */
2225 action = (msre_action *)telts[k].val;
2226 metadata = (msre_tfn_metadata *)action->param_data;
2227 tfnchanged = metadata->execute(mptmp,
2228 (unsigned char *)var->value, var->value_len,
2229 &rval, &rval_length);
2231 if (tfnchanged < 0) {
2239 /* Use the new values */
2241 var->value_len = rval_length;
2243 /* Cache the transformation */
2245 int tfnsnum = k + 1;
2247 /* Generate the cache key */
2248 tfnspath = apr_psprintf(msr->mp, "%s%s%s", (tfnspath ? tfnspath : ""),
2249 (tfnspath ? "," : ""), action->param);
2250 tfnskey = apr_psprintf(msr->mp, "%x;%s", tfnsnum, tfnspath);
2252 if ((msr->txcfg->cache_trans_maxitems != 0) &&
2253 (msr->tcache_items >= msr->txcfg->cache_trans_maxitems))
2255 /* Warn only once if we attempt to go over the cache limit. */
2256 if (msr->tcache_items == msr->txcfg->cache_trans_maxitems) {
2257 msr->tcache_items++;
2258 msr_log(msr, 4, "CACHE: Disabled - phase=%d"
2259 " maxitems=%" APR_SIZE_T_FMT
2262 msr->txcfg->cache_trans_maxitems);
2265 else if (msr->txcfg->cache_trans_incremental ||
2266 (tfnsnum == tarr->nelts))
2268 /* ENH1: Add flag to vars to tell which ones can change across phases store the rest in a global cache */
2269 crec = (msre_cache_rec *)apr_pcalloc(msr->mp, sizeof(msre_cache_rec));
2270 if (crec == NULL) return -1;
2273 crec->changed = changed;
2275 crec->path = tfnspath;
2277 /* We want to cache a copy if it changed otherwise
2278 * we just want to use a pointer to the last changed value.
2280 crec->val = (!lastvarval || tfnchanged) ? apr_pmemdup(msr->mp, var->value, var->value_len) : lastvarval;
2281 crec->val_len = changed ? ((!lastvarval || tfnchanged) ? var->value_len : lastvarlen) : 0;
2283 /* Keep track of the last changed var value */
2285 lastvarval = crec->val;
2286 lastvarlen = crec->val_len;
2291 msr_log(msr, 9, "CACHE: Caching %s=\"%s\" (%pp)",
2293 log_escape_nq_ex(mptmp,
2299 msr_log(msr, 9, "CACHE: Caching %s=<no change> (%pp)",
2305 msr->tcache_items++;
2307 apr_table_setn(cachetab, tfnskey, (void *)crec);
2311 if (msr->txcfg->debuglog_level >= 9) {
2312 msr_log(msr, 9, "T (%d) %s: \"%s\"", rc, metadata->name,
2313 log_escape_nq_ex(mptmp, var->value, var->value_len));
2318 /* Execute operator if multi-matching is not enabled,
2319 * or if it is and we need to process the result of the
2320 * last transformation.
2322 if (!multi_match || changed) {
2325 #if defined(PERFORMANCE_MEASUREMENT)
2327 apr_time_t t1 = apr_time_now();
2328 rule->trans_time += (t1 - time_before_trans);
2331 if (msr->txcfg->debuglog_level >= 4) {
2332 apr_time_t t1 = apr_time_now();
2334 msr_log(msr, 4, "Transformation completed in %" APR_TIME_T_FMT " usec.",
2335 (t1 - time_before_trans));
2339 rc = execute_operator(var, rule, msr, acting_actionset, mptmp);
2345 if (rc == RULE_MATCH) {
2348 /* Return straight away if the transaction
2349 * was intercepted - no need to process the remaining
2352 if (msr->rule_was_intercepted) {
2360 return (match_count ? RULE_MATCH : RULE_NO_MATCH);
2363 #if defined(WITH_LUA)
2367 static apr_status_t msre_rule_process_lua(msre_rule *rule, modsec_rec *msr) {
2368 msre_actionset *acting_actionset = NULL;
2369 char *my_error_msg = NULL;
2372 /* Choose the correct metadata/disruptive action actionset. */
2373 acting_actionset = rule->actionset;
2374 if (rule->chain_starter != NULL) {
2375 acting_actionset = rule->chain_starter->actionset;
2378 rc = lua_execute(rule->script, NULL, msr, rule, &my_error_msg);
2380 msr_log(msr, 1, "%s", my_error_msg);
2384 /* A non-NULL error message means the rule matched. */
2385 if (my_error_msg != NULL) {
2386 /* Perform non-disruptive actions. */
2387 msre_perform_nondisruptive_actions(msr, rule, rule->actionset, msr->msc_rule_mptmp);
2389 /* Perform disruptive actions, but only if
2390 * this rule is not part of a chain.
2392 if (rule->actionset->is_chained == 0) {
2393 msre_perform_disruptive_actions(msr, rule, acting_actionset, msr->msc_rule_mptmp, my_error_msg);
2404 apr_status_t msre_rule_process(msre_rule *rule, modsec_rec *msr) {
2405 /* Use a fresh memory sub-pool for processing each rule */
2406 if (msr->msc_rule_mptmp == NULL) {
2407 if (apr_pool_create(&msr->msc_rule_mptmp, msr->mp) != APR_SUCCESS) {
2411 apr_pool_clear(msr->msc_rule_mptmp);
2414 #if defined(WITH_LUA)
2415 if (rule->type == RULE_TYPE_LUA) {
2416 return msre_rule_process_lua(rule, msr);
2420 return msre_rule_process_normal(rule, msr);
2424 * Checks whether the given rule ID is in the given range.
2426 int rule_id_in_range(int ruleid, const char *range) {
2427 char *p = NULL, *saveptr = NULL;
2430 if (range == NULL) return 0;
2431 data = strdup(range);
2432 if (data == NULL) return 0;
2434 p = apr_strtok(data, ",", &saveptr);
2436 char *s = strstr(p, "-");
2438 if (ruleid == atoi(p)) {
2443 int start = atoi(p);
2444 int end = atoi(s + 1);
2445 if ((ruleid >= start)&&(ruleid <= end)) {
2450 p = apr_strtok(NULL, ",", &saveptr);