/* * ModSecurity for Apache 2.x, http://www.modsecurity.org/ * Copyright (c) 2004-2007 Breach Security, Inc. (http://www.breach.com/) * * You should have received a copy of the licence along with this * program (stored in the file "LICENSE"). If the file is missing, * or if you have any other questions related to the licence, please * write to Breach Security, Inc. at support@breach.com. * */ #include "re.h" #include "msc_pcre.h" #include "msc_geo.h" #include "apr_lib.h" #include "apr_strmatch.h" #include "acmp.h" /** * */ void msre_engine_op_register(msre_engine *engine, const char *name, FN_OP_PARAM_INIT(fn1), FN_OP_EXECUTE(fn2)) { msre_op_metadata *metadata = (msre_op_metadata *)apr_pcalloc(engine->mp, sizeof(msre_op_metadata)); if (metadata == NULL) return; metadata->name = name; metadata->param_init = fn1; metadata->execute = fn2; apr_table_setn(engine->operators, name, (void *)metadata); } /** * */ msre_op_metadata *msre_engine_op_resolve(msre_engine *engine, const char *name) { return (msre_op_metadata *)apr_table_get(engine->operators, name); } /* -- Operators -- */ /* unconditionalMatch */ static int msre_op_unconditionalmatch_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) { *error_msg = "Unconditional match in SecAction."; /* Always match. */ return 1; } /* rx */ static int msre_op_rx_param_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, PCRE_DOTALL | PCRE_DOLLAR_ENDONLY, &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_rx_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 capture = 0; int rc; 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; } /* Are we supposed to capture subexpressions? */ capture = apr_table_get(rule->actionset->actions, "capture") ? 1 : 0; /* Show when the regex captures but "capture" is not set */ if (msr->txcfg->debuglog_level >= 6) { int capcount = 0; rc = msc_fullinfo(regex, PCRE_INFO_CAPTURECOUNT, &capcount); if ((capture == 0) && (capcount > 0)) { msr_log(msr, 6, "Ignoring regex captures since \"capture\" action is not enabled."); } } /* 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 < -1) { *error_msg = apr_psprintf(msr->mp, "Regex execution failed: %s", my_error_msg); return -1; } /* Handle captured subexpressions. */ if (capture && rc > 0) { int i; /* Use the available captures. */ for(i = 0; i < rc; i++) { msc_string *s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); if (s == NULL) return -1; s->name = apr_psprintf(msr->mp, "%i", i); s->value = apr_pstrmemdup(msr->mp, target + ovector[2*i], ovector[2*i + 1] - ovector[2*i]); s->value_len = (ovector[2*i + 1] - ovector[2*i]); if ((s->name == NULL)||(s->value == NULL)) return -1; apr_table_setn(msr->tx_vars, s->name, (void *)s); if (msr->txcfg->debuglog_level >= 9) { msr_log(msr, 9, "Adding regex subexpression to TXVARS (%i): %s", i, log_escape_nq_ex(msr->mp, s->value, s->value_len)); } } /* Unset the remaining ones (from previous invocations). */ for(i = rc; i <= 9; i++) { char buf[24]; apr_snprintf(buf, sizeof(buf), "%i", i); apr_table_unset(msr->tx_vars, buf); } } /* if ( ((rc == PCRE_ERROR_NOMATCH)&&(rule->op_negated == 1)) || ((rc != PCRE_ERROR_NOMATCH)&&(rule->op_negated == 0)) ) { */ if (rc != PCRE_ERROR_NOMATCH) { /* Match. */ char *pattern_escaped = log_escape(msr->mp, regex->pattern); /* This message will be logged. */ if (strlen(pattern_escaped) > 252) { *error_msg = apr_psprintf(msr->mp, "Pattern match \"%.252s ...\" at %s.", pattern_escaped, var->name); } else { *error_msg = apr_psprintf(msr->mp, "Pattern match \"%s\" at %s.", pattern_escaped, var->name); } return 1; } /* No match. */ return 0; } /* pm */ static int msre_op_pm_param_init(msre_rule *rule, char **error_msg) { if ((rule->op_param == NULL)||(strlen(rule->op_param) == 0)) { *error_msg = apr_psprintf(rule->ruleset->mp, "Missing parameter for operator 'pm'."); return 0; /* ERROR */ } ACMP *p = acmp_create(0, rule->ruleset->mp); if (p == NULL) return 0; const char *phrase = apr_pstrdup(rule->ruleset->mp, rule->op_param); const char *next = rule->op_param + strlen(rule->op_param); /* Loop through phrases */ /* ENH: Need to allow quoted phrases w/space */ for (;;) { while((isspace(*phrase) != 0) && (*phrase != '\0')) phrase++; if (*phrase == '\0') break; next = phrase; while((isspace(*next) == 0) && (*next != 0)) next++; acmp_add_pattern(p, phrase, NULL, NULL, next - phrase); phrase = next; } acmp_prepare(p); rule->op_param_data = p; return 1; } /* pmFromFile */ static int msre_op_pmFromFile_param_init(msre_rule *rule, char **error_msg) { char errstr[1024]; char buf[HUGE_STRING_LEN + 1]; char *fn; char *next; char *ptr; const char *rulefile_path; apr_status_t rc; apr_file_t *fd; if ((rule->op_param == NULL)||(strlen(rule->op_param) == 0)) { *error_msg = apr_psprintf(rule->ruleset->mp, "Missing parameter for operator 'pm'."); return 0; /* ERROR */ } ACMP *p = acmp_create(0, rule->ruleset->mp); if (p == NULL) return 0; fn = apr_pstrdup(rule->ruleset->mp, rule->op_param); next = fn + strlen(rule->op_param); /* 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 fprintf(stderr, "Rulefile path: \"%s\"\n", rulefile_path); #endif /* Loop through filenames */ /* ENH: Need to allow quoted filenames w/space */ for (;;) { const char *rootpath = NULL; const char *filepath = NULL; int line = 0; /* Trim whitespace */ while((isspace(*fn) != 0) && (*fn != '\0')) fn++; if (*fn == '\0') break; next = fn; while((isspace(*next) == 0) && (*next != '\0')) next++; while((isspace(*next) != 0) && (*next != '\0')) *next++ = '\0'; /* Add path of the rule filename for a relative phrase filename */ filepath = fn; if (apr_filepath_root(&rootpath, &filepath, APR_FILEPATH_TRUENAME, rule->ruleset->mp) != APR_SUCCESS) { /* We are not an absolute path. It could mean an error, but * let that pass through to the open call for a better error */ apr_filepath_merge(&fn, rulefile_path, fn, APR_FILEPATH_TRUENAME, rule->ruleset->mp); } /* Open file and read */ rc = apr_file_open(&fd, fn, APR_READ | APR_FILE_NOCLEANUP, 0, rule->ruleset->mp); if (rc != APR_SUCCESS) { *error_msg = apr_psprintf(rule->ruleset->mp, "Could not open phrase file \"%s\": %s", fn, apr_strerror(rc, errstr, 1024)); return 0; } #ifdef DEBUG_CONF fprintf(stderr, "Loading phrase file: \"%s\"\n", fn); #endif /* Read one pattern per line skipping empty/commented */ for(;;) { line++; rc = apr_file_gets(buf, HUGE_STRING_LEN, fd); if (rc == APR_EOF) break; if (rc != APR_SUCCESS) { *error_msg = apr_psprintf(rule->ruleset->mp, "Could read \"%s\" line %d: %s", fn, line, apr_strerror(rc, errstr, 1024)); return 0; } /* Remove newline */ ptr = buf; while(*ptr != '\0') ptr++; if ((ptr > buf) && (*(ptr - 1) == '\n')) *(ptr - 1) = '\0'; /* Ignore empty lines and comments */ ptr = buf; while((*ptr != '\0') && apr_isspace(*ptr)) ptr++; if ((*ptr == '\0') || (*ptr == '#')) continue; #ifdef DEBUG_CONF fprintf(stderr, "Adding phrase file pattern: \"%s\"\n", buf); #endif acmp_add_pattern(p, buf, NULL, NULL, strlen(buf)); } fn = next; } if (fd != NULL) apr_file_close(fd); acmp_prepare(p); rule->op_param_data = p; return 1; } static int msre_op_pm_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) { const char *match = NULL; apr_status_t rc = 0; /* Nothing to read */ if ((var->value == NULL) || (var->value_len == 0)) return 0; ACMPT pt = {(ACMP *)rule->op_param_data, NULL}; rc = acmp_process_quick(&pt, &match, var->value, var->value_len); if (rc) { char *match_escaped = log_escape(msr->mp, match ? match : ""); /* This message will be logged. */ if (strlen(match_escaped) > 252) { *error_msg = apr_psprintf(msr->mp, "Matched phrase \"%.252s ...\" at %s.", match_escaped, var->name); } else { *error_msg = apr_psprintf(msr->mp, "Matched phrase \"%s\" at %s.", match_escaped, var->name); } return 1; } return rc; } /* contains */ static int msre_op_contains_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) { msc_string *str = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); const char *match = NULL; const char *target; unsigned int match_length; unsigned int target_length; unsigned int i, i_max; str->value = (char *)rule->op_param; str->value_len = strlen(str->value); if (error_msg == NULL) return -1; *error_msg = NULL; if (str->value == NULL) { *error_msg = "Internal Error: match string is null."; return -1; } expand_macros(msr, str, rule, msr->mp); match = (const char *)str->value; match_length = str->value_len; /* 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; } /* These are impossible to match */ if ((match_length == 0) || (match_length > target_length)) { /* No match. */ return 0; } /* scan for first character, then compare from there until we * have a match or there is no room left in the target */ i_max = target_length - match_length; for (i = 0; i <= i_max; i++) { if (target[i] == match[0]) { if (strncmp(match, (target + i), match_length) == 0) { /* Match. */ *error_msg = apr_psprintf(msr->mp, "String match \"%s\" at %s.", log_escape_ex(msr->mp, match, match_length), var->name); return 1; } } } /* No match. */ return 0; } /* streq */ static int msre_op_streq_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) { msc_string *str = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); const char *match = NULL; const char *target; unsigned int match_length; unsigned int target_length; str->value = (char *)rule->op_param; str->value_len = strlen(str->value); if (error_msg == NULL) return -1; *error_msg = NULL; if (str->value == NULL) { *error_msg = "Internal Error: match string is null."; return -1; } expand_macros(msr, str, rule, msr->mp); match = (const char *)str->value; match_length = str->value_len; /* 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; } /* These are impossible to match */ if (match_length != target_length) { /* No match. */ return 0; } if (strncmp(match, target, target_length) == 0) { /* Match. */ *error_msg = apr_psprintf(msr->mp, "String match \"%s\" at %s.", log_escape_ex(msr->mp, match, match_length), var->name); return 1; } /* No match. */ return 0; } /* beginsWith */ static int msre_op_beginsWith_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) { msc_string *str = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); const char *match = NULL; const char *target; unsigned int match_length; unsigned int target_length; str->value = (char *)rule->op_param; str->value_len = strlen(str->value); if (error_msg == NULL) return -1; *error_msg = NULL; if (str->value == NULL) { *error_msg = "Internal Error: match string is null."; return -1; } expand_macros(msr, str, rule, msr->mp); match = (const char *)str->value; match_length = str->value_len; /* 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; } /* These are impossible to match */ if ((match_length == 0) || (match_length > target_length)) { /* No match. */ return 0; } if (strncmp(match, target, match_length) == 0) { /* Match. */ *error_msg = apr_psprintf(msr->mp, "String match \"%s\" at %s.", log_escape_ex(msr->mp, match, match_length), var->name); return 1; } /* No match. */ return 0; } /* endsWith */ static int msre_op_endsWith_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) { msc_string *str = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); const char *match = NULL; const char *target; unsigned int match_length; unsigned int target_length; str->value = (char *)rule->op_param; str->value_len = strlen(str->value); if (error_msg == NULL) return -1; *error_msg = NULL; if (str->value == NULL) { *error_msg = "Internal Error: match string is null."; return -1; } expand_macros(msr, str, rule, msr->mp); match = (const char *)str->value; match_length = str->value_len; /* 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; } /* These are impossible to match */ if ((match_length == 0) || (match_length > target_length)) { /* No match. */ return 0; } if (strncmp(match, (target + (target_length - match_length)), match_length) == 0) { /* Match. */ *error_msg = apr_psprintf(msr->mp, "String match \"%s\" at %s.", log_escape_ex(msr->mp, match, match_length), var->name); return 1; } /* No match. */ return 0; } /* m */ static int msre_op_m_param_init(msre_rule *rule, char **error_msg) { const apr_strmatch_pattern *compiled_pattern; const char *pattern = rule->op_param; if (error_msg == NULL) return -1; *error_msg = NULL; /* Compile pattern */ compiled_pattern = apr_strmatch_precompile(rule->ruleset->mp, pattern, 1); if (compiled_pattern == NULL) { *error_msg = apr_psprintf(rule->ruleset->mp, "Error compiling pattern: %s", pattern); return 0; } rule->op_param_data = (void *)compiled_pattern; return 1; /* OK */ } static int msre_op_m_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) { apr_strmatch_pattern *compiled_pattern = (apr_strmatch_pattern *)rule->op_param_data; const char *target; unsigned int target_length; const char *rc; if (error_msg == NULL) return -1; *error_msg = NULL; if (compiled_pattern == NULL) { *error_msg = "Internal Error: strnmatch 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 = apr_strmatch(compiled_pattern, target, target_length); if (rc == NULL) { /* No match. */ return 0; } *error_msg = apr_psprintf(msr->mp, "Pattern match \"%s\" at %s.", log_escape(msr->mp, rule->op_param), var->name); /* Match. */ return 1; } #ifdef WITH_LIBXML2 /* validateDTD */ static int msre_op_validateDTD_init(msre_rule *rule, char **error_msg) { /* ENH Verify here the file actually exists. */ return 1; } static int msre_op_validateDTD_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) { xmlValidCtxtPtr cvp; xmlDtdPtr dtd; if ((msr->xml == NULL)||(msr->xml->doc == NULL)) { *error_msg = apr_psprintf(msr->mp, "XML document tree could not be found for " "DTD validation."); return -1; } dtd = xmlParseDTD(NULL, (const xmlChar *)rule->op_param); /* EHN support relative filenames */ if (dtd == NULL) { *error_msg = apr_psprintf(msr->mp, "XML: Failed to load DTD: %s", rule->op_param); return -1; } cvp = xmlNewValidCtxt(); if (cvp == NULL) { *error_msg = "XML: Failed to create a validation context."; xmlFreeDtd(dtd); return -1; } if (!xmlValidateDtd(cvp, msr->xml->doc, dtd)) { *error_msg = "XML: DTD validation failed."; xmlFreeValidCtxt(cvp); xmlFreeDtd(dtd); return 1; /* No match. */ } msr_log(msr, 4, "XML: Successfully validated payload against DTD: %s", rule->op_param); xmlFreeValidCtxt(cvp); xmlFreeDtd(dtd); /* Match. */ return 0; } /* validateSchema */ static int msre_op_validateSchema_init(msre_rule *rule, char **error_msg) { /* ENH Verify here the file actually exists. */ return 1; } static int msre_op_validateSchema_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) { xmlSchemaParserCtxtPtr parserCtx; xmlSchemaValidCtxtPtr validCtx; xmlSchemaPtr schema; int rc; if ((msr->xml == NULL)||(msr->xml->doc == NULL)) { *error_msg = apr_psprintf(msr->mp, "XML document tree could not be found for " "Schema validation."); return -1; } parserCtx = xmlSchemaNewParserCtxt(rule->op_param); /* ENH support relative filenames */ if (parserCtx == NULL) { *error_msg = apr_psprintf(msr->mp, "XML: Failed to load Schema from file: %s", rule->op_param); return -1; } schema = xmlSchemaParse(parserCtx); if (schema == NULL) { *error_msg = apr_psprintf(msr->mp, "XML: Failed to load Schema: %s", rule->op_param); xmlSchemaFreeParserCtxt(parserCtx); return -1; } validCtx = xmlSchemaNewValidCtxt(schema); if (validCtx == NULL) { *error_msg = "XML: Failed to create validation context."; xmlSchemaFree(schema); xmlSchemaFreeParserCtxt(parserCtx); return -1; } rc = xmlSchemaValidateDoc(validCtx, msr->xml->doc); if (rc != 0) { *error_msg = "XML: Schema validation failed."; xmlSchemaFree(schema); xmlSchemaFreeParserCtxt(parserCtx); return 1; /* No match. */ } msr_log(msr, 4, "XML: Successfully validated payload against Schema: %s", rule->op_param); xmlSchemaFree(schema); xmlSchemaFreeValidCtxt(validCtx); return 0; } #endif /** * Perform geograpical lookups on an IP/Host. */ static int msre_op_geoLookup_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) { geo_rec rec; geo_db *geo = msr->txcfg->geo; const char *geo_host = var->value; msc_string *s = NULL; int rc; if (geo == NULL) { msr_log(msr, 1, "Geo lookup for \"%s\" attempted without a database. Set SecGeoLookupDb.", geo_host); return 0; } rc = geo_lookup(msr, &rec, geo_host, error_msg); if (rc <= 0) { return rc; } if (msr->txcfg->debuglog_level >= 9) { msr_log(msr, 9, "GEO: %s={country_code=%s, country_code3=%s, country_name=%s, country_continent=%s, region=%s, city=%s, postal_code=%s, latitude=%f, longitude=%f, dma_code=%d, area_code=%d}", geo_host, rec.country_code, rec.country_code3, rec.country_name, rec.country_continent, rec.region, rec.city, rec.postal_code, rec.latitude, rec.longitude, rec.dma_code, rec.area_code); } s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); s->name = apr_pstrdup(msr->mp, "country_code"); s->value = apr_pstrdup(msr->mp, rec.country_code ? rec.country_code : ""); s->value_len = strlen(s->value); apr_table_setn(msr->geo_vars, s->name, (void *)s); s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); s->name = apr_pstrdup(msr->mp, "country_code3"); s->value = apr_pstrdup(msr->mp, rec.country_code3 ? rec.country_code3 : ""); s->value_len = strlen(s->value); apr_table_setn(msr->geo_vars, s->name, (void *)s); s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); s->name = apr_pstrdup(msr->mp, "region"); s->value = apr_pstrdup(msr->mp, rec.region ? rec.region : ""); s->value_len = strlen(s->value); apr_table_setn(msr->geo_vars, s->name, (void *)s); s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); s->name = apr_pstrdup(msr->mp, "city"); s->value = apr_pstrdup(msr->mp, rec.city ? rec.city : ""); s->value_len = strlen(s->value); apr_table_setn(msr->geo_vars, s->name, (void *)s); s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); s->name = apr_pstrdup(msr->mp, "postal_code"); s->value = apr_pstrdup(msr->mp, rec.postal_code ? rec.postal_code : ""); s->value_len = strlen(s->value); apr_table_setn(msr->geo_vars, s->name, (void *)s); s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); s->name = apr_pstrdup(msr->mp, "latitude"); s->value = apr_psprintf(msr->mp, "%f", rec.latitude); s->value_len = strlen(s->value); apr_table_setn(msr->geo_vars, s->name, (void *)s); s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); s->name = apr_pstrdup(msr->mp, "longitude"); s->value = apr_psprintf(msr->mp, "%f", rec.longitude); s->value_len = strlen(s->value); apr_table_setn(msr->geo_vars, s->name, (void *)s); s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); s->name = apr_pstrdup(msr->mp, "dma_code"); s->value = apr_psprintf(msr->mp, "%d", rec.dma_code); s->value_len = strlen(s->value); apr_table_setn(msr->geo_vars, s->name, (void *)s); s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string)); s->name = apr_pstrdup(msr->mp, "area_code"); s->value = apr_psprintf(msr->mp, "%d", rec.area_code); s->value_len = strlen(s->value); apr_table_setn(msr->geo_vars, s->name, (void *)s); return 1; } /* rbl */ static int msre_op_rbl_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) { unsigned int h0, h1, h2, h3; char *name_to_check = NULL; char *target = NULL; apr_sockaddr_t *sa = NULL; apr_status_t rc; if (error_msg == NULL) return -1; *error_msg = NULL; /* ENH Add IPv6 support. */ target = apr_pstrmemdup(msr->mp, var->value, var->value_len); if (target == NULL) return -1; /* Construct the host name we want to resolve. */ if (sscanf(target, "%d.%d.%d.%d", &h0, &h1, &h2, &h3) == 4) { /* IPv4 address */ name_to_check = apr_psprintf(msr->mp, "%i.%i.%i.%i.%s", h3, h2, h1, h0, rule->op_param); } else { /* Assume the input is a domain name. */ name_to_check = apr_psprintf(msr->mp, "%s.%s", target, rule->op_param); } if (name_to_check == NULL) return -1; rc = apr_sockaddr_info_get(&sa, name_to_check, APR_UNSPEC/*msr->r->connection->remote_addr->family*/, 0, 0, msr->mp); if (rc == APR_SUCCESS) { *error_msg = apr_psprintf(msr->r->pool, "RBL lookup of %s succeeded.", log_escape_nq(msr->mp, name_to_check)); return 1; /* Match. */ } msr_log(msr, 5, "RBL lookup of %s failed.", log_escape_nq(msr->mp, name_to_check)); /* No match. */ return 0; } /* inspectFile */ static int msre_op_inspectFile_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) { char *script_output = NULL; char const *argv[5]; const char *approver_script = rule->op_param; const char *target_file = apr_pstrmemdup(msr->mp, var->value, var->value_len); if (error_msg == NULL) return -1; *error_msg = NULL; msr_log(msr, 4, "Executing %s to inspect %s.", approver_script, target_file); argv[0] = approver_script; argv[1] = target_file; argv[2] = NULL; if (apache2_exec(msr, approver_script, (const char **)argv, &script_output) <= 0) { *error_msg = apr_psprintf(msr->mp, "Execution of the approver script \"%s\" failed (invocation failed).", log_escape(msr->mp, approver_script)); return -1; } if (script_output == NULL) { *error_msg = apr_psprintf(msr->mp, "Execution of the approver script \"%s\" failed (no output).", log_escape(msr->mp, approver_script)); return -1; } if (script_output[0] != '1') { *error_msg = apr_psprintf(msr->mp, "File \"%s\" rejected by the approver script \"%s\": %s", log_escape(msr->mp, target_file), log_escape(msr->mp, approver_script), log_escape_nq(msr->mp, script_output)); return 1; /* Match. */ } /* No match. */ return 0; } /* validateByteRange */ static int msre_op_validateByteRange_init(msre_rule *rule, char **error_msg) { char *p = NULL, *saveptr = NULL; char *table = NULL, *data = NULL; if (error_msg == NULL) return -1; *error_msg = NULL; if (rule->op_param == NULL) { *error_msg = apr_psprintf(rule->ruleset->mp, "Missing parameter for validateByteRange."); return -1; } /* Initialise. */ data = apr_pstrdup(rule->ruleset->mp, rule->op_param); rule->op_param_data = apr_pcalloc(rule->ruleset->mp, 32); if ((data == NULL)||(rule->op_param_data == NULL)) return -1; table = rule->op_param_data; /* Extract parameters and update table. */ p = apr_strtok(data, ",", &saveptr); while(p != NULL) { char *s = strstr(p, "-"); if (s == NULL) { /* Single value. */ int x = atoi(p); if ((x < 0)||(x > 255)) { *error_msg = apr_psprintf(rule->ruleset->mp, "Invalid range value: %i", x); return 0; } table[x>>3] = (table[x>>3] | (1 << (x & 0x7))); } else { /* Range. */ int start = atoi(p); int end = atoi(s + 1); if ((start < 0)||(start > 255)) { *error_msg = apr_psprintf(rule->ruleset->mp, "Invalid range start value: %i", start); return 0; } if ((end < 0)||(end > 255)) { *error_msg = apr_psprintf(rule->ruleset->mp, "Invalid range end value: %i", end); return 0; } if (start > end) { *error_msg = apr_psprintf(rule->ruleset->mp, "Invalid range: %i-%i", start, end); return 0; } while(start <= end) { table[start >> 3] = (table[start >> 3] | (1 << (start & 0x7))); start++; } } p = apr_strtok(NULL, ",", &saveptr); } return 1; } static int msre_op_validateByteRange_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) { char *table = rule->op_param_data; unsigned int i, count; if (error_msg == NULL) return -1; *error_msg = NULL; if (table == NULL) { *error_msg = apr_psprintf(msr->mp, "Internal Error: validateByteRange table not " "initialised."); return -1; } /* Check every byte of the target to detect characters that are not allowed. */ count = 0; for(i = 0; i < var->value_len; i++) { int x = ((unsigned char *)var->value)[i]; if (!(table[x >> 3] & (1 << (x & 0x7)))) { if (msr->txcfg->debuglog_level >= 9) { msr_log(msr, 9, "Value %i outside range: %s", x, rule->op_param); } count++; } } if (count == 0) return 0; /* Valid - no match. */ *error_msg = apr_psprintf(msr->mp, "Found %i byte(s) outside range: %s.", count, rule->op_param); return 1; /* Invalid - match.*/ } /* validateUrlEncoding */ static int validate_url_encoding(const char *input, long int input_length) { int i; if ((input == NULL)||(input_length < 0)) return -1; i = 0; while (i < input_length) { if (input[i] == '%') { if (i + 2 >= input_length) { /* Not enough bytes. */ return -3; } else { /* Here we only decode a %xx combination if it is valid, * leaving it as is otherwise. */ char c1 = input[i + 1]; char c2 = input[i + 2]; if ( (((c1 >= '0')&&(c1 <= '9')) || ((c1 >= 'a')&&(c1 <= 'f')) || ((c1 >= 'A')&&(c1 <= 'F'))) && (((c2 >= '0')&&(c2 <= '9')) || ((c2 >= 'a')&&(c2 <= 'f')) || ((c2 >= 'A')&&(c2 <= 'F'))) ) { i += 3; } else { /* Non-hexadecimal characters used in encoding. */ return -2; } } } else { i++; } } return 1; } static int msre_op_validateUrlEncoding_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) { int rc = validate_url_encoding(var->value, var->value_len); switch(rc) { case 1 : return 0; /* Encoding is valid, no match. */ break; case -2 : *error_msg = apr_psprintf(msr->mp, "Invalid URL Encoding: Non-hexadecimal " "digits used."); return 1; /* Invalid, match. */ break; case -3 : *error_msg = apr_psprintf(msr->mp, "Invalid URL Encoding: Not enough characters " "at the end of input."); return 1; /* Invalid, match. */ break; case -1 : default : *error_msg = apr_psprintf(msr->mp, "Invalid URL Encoding: Internal Error (rc = %i)", rc); return -1; break; } /* No match. */ return 0; } /* validateUtf8Encoding */ #define UNICODE_ERROR_CHARACTERS_MISSING -1 #define UNICODE_ERROR_INVALID_ENCODING -2 #define UNICODE_ERROR_OVERLONG_CHARACTER -3 static int detect_utf8_character(const char *p_read, unsigned int length) { int unicode_len = 0; unsigned int d = 0; unsigned char c; if (p_read == NULL) return 0; c = *p_read; if (c == 0) return 0; if ((c & 0xE0) == 0xC0) { /* two byte unicode */ if (length < 2) unicode_len = UNICODE_ERROR_CHARACTERS_MISSING; else if (((*(p_read + 1)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; else { unicode_len = 2; d = ((c & 0x1F) << 6) | (*(p_read + 1) & 0x3F); } } else if ((c & 0xF0) == 0xE0) { /* three byte unicode */ if (length < 3) unicode_len = UNICODE_ERROR_CHARACTERS_MISSING; else if (((*(p_read + 1)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; else if (((*(p_read + 2)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; else { unicode_len = 3; d = ((c & 0x0F) << 12) | ((*(p_read + 1) & 0x3F) << 6) | (*(p_read + 2) & 0x3F); } } else if ((c & 0xF8) == 0xF0) { /* four byte unicode */ if (length < 4) unicode_len = UNICODE_ERROR_CHARACTERS_MISSING; else if (((*(p_read + 1)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; else if (((*(p_read + 2)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; else if (((*(p_read + 3)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; else { d = ((c & 0x07) << 18) | ((*(p_read + 1) & 0x3F) << 12) | ((*(p_read + 2) & 0x3F) < 6) | (*(p_read + 3) & 0x3F); unicode_len = 4; } } else if ((c & 0xFC) == 0xF8) { /* five byte unicode */ if (length < 5) unicode_len = UNICODE_ERROR_CHARACTERS_MISSING; else if (((*(p_read + 1)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; else if (((*(p_read + 2)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; else if (((*(p_read + 3)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; else if (((*(p_read + 4)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; else { d = ((c & 0x03) << 24) | ((*(p_read + 1) & 0x3F) << 18) | ((*(p_read + 2) & 0x3F) << 12) | ((*(p_read + 3) & 0x3F) << 6) | (*(p_read + 4) & 0x3F); unicode_len = 5; } } else if ((c & 0xFE) == 0xFC) { /* six byte unicode */ if (length < 6) unicode_len = UNICODE_ERROR_CHARACTERS_MISSING; else if (((*(p_read + 1)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; else if (((*(p_read + 2)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; else if (((*(p_read + 3)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; else if (((*(p_read + 4)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; else if (((*(p_read + 5)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; else { d = ((c & 0x01) << 30) | ((*(p_read + 1) & 0x3F) << 24) | ((*(p_read + 2) & 0x3F) << 18) | ((*(p_read + 3) & 0x3F) << 12) | ((*(p_read + 4) & 0x3F) << 6) | (*(p_read + 5) & 0x3F); unicode_len = 6; } } if ((unicode_len > 1)&&((d & 0x7F) == d)) { unicode_len = UNICODE_ERROR_OVERLONG_CHARACTER; } return unicode_len; } static int msre_op_validateUtf8Encoding_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) { unsigned int i, bytes_left; bytes_left = var->value_len; for(i = 0; i < var->value_len; i++) { int rc = detect_utf8_character(&var->value[i], bytes_left); switch(rc) { case UNICODE_ERROR_CHARACTERS_MISSING : *error_msg = apr_psprintf(msr->mp, "Invalid UTF-8 encoding: not enough bytes in " "character."); return 1; break; case UNICODE_ERROR_INVALID_ENCODING : *error_msg = apr_psprintf(msr->mp, "Invalid Unicode encoding: invalid byte value " "in character."); return 1; break; case UNICODE_ERROR_OVERLONG_CHARACTER : *error_msg = apr_psprintf(msr->mp, "Invalid Unicode encoding: overlong " "character detected."); return 1; break; } bytes_left--; } return 0; } /* eq */ static int msre_op_eq_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) { int left, right; char *target = NULL; if ((var->value == NULL)||(rule->op_param == NULL)) { /* NULL values do not match anything. */ return 0; } target = apr_pstrmemdup(msr->mp, var->value, var->value_len); if (target == NULL) return -1; left = atoi(target); right = atoi(rule->op_param); if (left != right) { /* No match. */ return 0; } else { *error_msg = apr_psprintf(msr->mp, "Operator EQ match: %i.", right); /* Match. */ return 1; } } /* gt */ static int msre_op_gt_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) { int left, right; char *target = NULL; if ((var->value == NULL)||(rule->op_param == NULL)) { /* NULL values do not match anything. */ return 0; } target = apr_pstrmemdup(msr->mp, var->value, var->value_len); if (target == NULL) return -1; left = atoi(target); right = atoi(rule->op_param); if (left <= right) { /* No match. */ return 0; } else { *error_msg = apr_psprintf(msr->mp, "Operator GT match: %i.", right); /* Match. */ return 1; } } /* lt */ static int msre_op_lt_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) { int left, right; char *target = NULL; if ((var->value == NULL)||(rule->op_param == NULL)) { /* NULL values do not match anything. */ return 0; } target = apr_pstrmemdup(msr->mp, var->value, var->value_len); if (target == NULL) return -1; left = atoi(target); right = atoi(rule->op_param); if (left >= right) { /* No match. */ return 0; } else { *error_msg = apr_psprintf(msr->mp, "Operator LT match: %i.", right); /* Match. */ return 1; } } /* ge */ static int msre_op_ge_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) { int left, right; char *target = NULL; if ((var->value == NULL)||(rule->op_param == NULL)) { /* NULL values do not match anything. */ return 0; } target = apr_pstrmemdup(msr->mp, var->value, var->value_len); if (target == NULL) return -1; left = atoi(target); right = atoi(rule->op_param); if (left < right) { /* No match. */ return 0; } else { *error_msg = apr_psprintf(msr->mp, "Operator GE match: %i.", right); /* Match. */ return 1; } } /* le */ static int msre_op_le_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) { int left, right; char *target = NULL; if ((var->value == NULL)||(rule->op_param == NULL)) { /* NULL values do not match anything. */ return 0; } target = apr_pstrmemdup(msr->mp, var->value, var->value_len); if (target == NULL) return -1; left = atoi(target); right = atoi(rule->op_param); if (left > right) { /* No match. */ return 0; } else { *error_msg = apr_psprintf(msr->mp, "Operator LE match: %i.", right); /* Match. */ return 1; } } /* ------------------------------------------------------------------------------- */ /** * */ void msre_engine_register_default_operators(msre_engine *engine) { /* unconditionalMatch */ msre_engine_op_register(engine, "unconditionalMatch", NULL, msre_op_unconditionalmatch_execute ); /* rx */ msre_engine_op_register(engine, "rx", msre_op_rx_param_init, msre_op_rx_execute ); /* pm */ msre_engine_op_register(engine, "pm", msre_op_pm_param_init, msre_op_pm_execute ); /* pmFromFile */ msre_engine_op_register(engine, "pmFromFile", msre_op_pmFromFile_param_init, msre_op_pm_execute ); /* contains */ msre_engine_op_register(engine, "contains", NULL, /* ENH init function to flag var substitution */ msre_op_contains_execute ); /* is */ msre_engine_op_register(engine, "streq", NULL, /* ENH init function to flag var substitution */ msre_op_streq_execute ); /* beginsWith */ msre_engine_op_register(engine, "beginsWith", NULL, /* ENH init function to flag var substitution */ msre_op_beginsWith_execute ); /* endsWith */ msre_engine_op_register(engine, "endsWith", NULL, /* ENH init function to flag var substitution */ msre_op_endsWith_execute ); /* m */ msre_engine_op_register(engine, "m", msre_op_m_param_init, msre_op_m_execute ); #ifdef WITH_LIBXML2 /* validateDTD */ msre_engine_op_register(engine, "validateDTD", msre_op_validateDTD_init, msre_op_validateDTD_execute ); /* validateSchema */ msre_engine_op_register(engine, "validateSchema", msre_op_validateSchema_init, msre_op_validateSchema_execute ); #endif /* geoLookup */ msre_engine_op_register(engine, "geoLookup", NULL, msre_op_geoLookup_execute ); /* rbl */ msre_engine_op_register(engine, "rbl", NULL, /* ENH init function to validate DNS server */ msre_op_rbl_execute ); /* inspectFile */ msre_engine_op_register(engine, "inspectFile", NULL, msre_op_inspectFile_execute ); /* validateByteRange */ msre_engine_op_register(engine, "validateByteRange", msre_op_validateByteRange_init, msre_op_validateByteRange_execute ); /* validateUrlEncoding */ msre_engine_op_register(engine, "validateUrlEncoding", NULL, msre_op_validateUrlEncoding_execute ); /* validateUtf8Encoding */ msre_engine_op_register(engine, "validateUtf8Encoding", NULL, msre_op_validateUtf8Encoding_execute ); /* eq */ msre_engine_op_register(engine, "eq", NULL, msre_op_eq_execute ); /* gt */ msre_engine_op_register(engine, "gt", NULL, msre_op_gt_execute ); /* lt */ msre_engine_op_register(engine, "lt", NULL, msre_op_lt_execute ); /* le */ msre_engine_op_register(engine, "le", NULL, msre_op_le_execute ); /* ge */ msre_engine_op_register(engine, "ge", NULL, msre_op_ge_execute ); }