diff --git a/apache2/re_operators.c b/apache2/re_operators.c index f52e7d72..30d6e338 100644 --- a/apache2/re_operators.c +++ b/apache2/re_operators.c @@ -916,6 +916,179 @@ static int msre_op_validateSchema_execute(modsec_rec *msr, msre_rule *rule, msre return 0; } +/* verifyCC */ + +static int validate_cc (modsec_rec *msr, const char *ccnumber, int len) { + char ccdigits[16]; + char *cdst = ccdigits; + const char *csrc = ccnumber; + int digits = 0; + int srclen = 0; + int multiply; + int digit; + int add; + int sum = 0; + + /* Remove non digits characters */ + + while ((srclen++ <= len) && (digits < 16)) { + if (isdigit(*csrc)) { + *(cdst++) = *csrc - 48; + ++digits; + } + csrc++; + + msr_log(msr, 9, "cdst[%d]: %.*s", digits, digits, ccdigits); + } + + /* Credit card checksum algorithm */ + + for (digit = digits - 1, multiply = 0; digit >= 0; digit--) { + if (multiply) { + add = ccdigits[digit] * 2; + } + else { + add = ccdigits[digit]; + } + + if (add > 9) { + sum += add - 9; + } + else { + sum += add; + } + + multiply = 1 - multiply; + } + + /* See if the final digit is a zero, if so the card is valid. */ + if ((sum % 10) != 0) { + return 0; + } else { + return 1; + } +} + +static int msre_op_verifyCC_init(msre_rule *rule, char **error_msg) { + const char *errptr = NULL; + int erroffset; + msc_regex_t *regex; + const char *pattern = rule->op_param; + + if (error_msg == NULL) return -1; + *error_msg = NULL; + + /* Compile pattern */ + regex = msc_pregcomp(rule->ruleset->mp, pattern, 0, &errptr, &erroffset); + if (regex == NULL) { + *error_msg = apr_psprintf(rule->ruleset->mp, "Error compiling pattern (pos %i): %s", + erroffset, errptr); + return 0; + } + + rule->op_param_data = regex; + + return 1; /* OK */ +} + +static int msre_op_verifyCC_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) { + msc_regex_t *regex = (msc_regex_t *)rule->op_param_data; + const char *target; + unsigned int target_length; + char *my_error_msg = NULL; + int ovector[33]; + int rc; + int is_cc = 0; + + if (error_msg == NULL) return -1; + *error_msg = NULL; + + if (regex == NULL) { + *error_msg = "Internal Error: regex data is null."; + return -1; + } + + /* 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; + } + + rc = msc_regexec_capture(regex, target, target_length, ovector, 30, &my_error_msg); + if (rc < -1) { + *error_msg = apr_psprintf(msr->mp, "Regex execution failed: %s", my_error_msg); + return -1; + } + + /* Handle captured subexpressions. */ + if (rc > 0) { + int capture = 0; + const apr_array_header_t *tarr; + const apr_table_entry_t *telts; + int i; + + /* Are we supposed to store the captured subexpressions? */ + /* IMP1 Can we use a flag to avoid having to iterate through the list every time. */ + tarr = apr_table_elts(rule->actionset->actions); + telts = (const apr_table_entry_t*)tarr->elts; + for (i = 0; i < tarr->nelts; i++) { + msre_action *action = (msre_action *)telts[i].val; + if (strcasecmp(action->metadata->name, "capture") == 0) { + capture = 1; + break; + } + } + + int k; + + /* Use the available captures. */ + /* to avoid memory manipulation I use the same captures for now to check for CC# */ + for(k = 0; k < rc; k++) { + msc_string *s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); + if (s == NULL) return -1; + s->name = apr_psprintf(msr->mp, "%i", k); + s->value = apr_pstrmemdup(msr->mp, + target + ovector[2*k], ovector[2*k + 1] - ovector[2*k]); + s->value_len = (ovector[2*k + 1] - ovector[2*k]); + if ((s->name == NULL)||(s->value == NULL)) return -1; + apr_table_setn(msr->tx_vars, s->name, (void *)s); + + msr_log(msr, 9, "Adding regex subexpression to TXVARS (%i): %s", k, + log_escape_nq(msr->mp, s->value)); + + if ((k > 0) && validate_cc (msr, s->value, s->value_len)) { + is_cc = 1; + } + } + + /* Unset the remaining ones (from previous invocations). */ + for(k = rc; k <= 9; k++) { + char buf[24]; + apr_snprintf(buf, sizeof(buf), "%i", k); + apr_table_unset(msr->tx_vars, buf); + } + } + + if ((rc != PCRE_ERROR_NOMATCH) && is_cc) { + /* Match. */ + + /* This message will be logged. */ + *error_msg = apr_psprintf(msr->mp, "CC# match \"%s\" at %s.", + regex->pattern, var->name); + return 1; + } + + /* No match. */ + return 0; +} + + #endif /** @@ -1654,6 +1827,13 @@ void msre_engine_register_default_operators(msre_engine *engine) { #endif + /* verifyCC */ + msre_engine_op_register(engine, + "verifyCC", + msre_op_verifyCC_init, + msre_op_verifyCC_execute + ); + /* geoLookup */ msre_engine_op_register(engine, "geoLookup", diff --git a/doc/modsecurity2-apache-reference.xml b/doc/modsecurity2-apache-reference.xml index 86121ac6..c680a220 100644 --- a/doc/modsecurity2-apache-reference.xml +++ b/doc/modsecurity2-apache-reference.xml @@ -4981,6 +4981,23 @@ SecRule XML "@validateSchema /path/to/apache2/conf/xml.xsd, +
+ <literal>verifyCC</literal> + + Description: This operator verifies a given + regular expression as a potential credit card number. It first matches + with a single generic regular expression then runs the resulting match + through a Luhn checksum algorithm to further verify it as a potential + credit card number. + + Example: + + SecRule ARGS "@verifyCC \d{13,16}" \ + "phase:2,sanitiseMatched,log,auditlog,pass,msg:'Potential credit card number'" +
+ +
+
<literal>within</literal>