/*
* ModSecurity for Apache 2.x, http://www.modsecurity.org/
* Copyright (c) 2004-2013 Trustwave Holdings, Inc. (http://www.trustwave.com/)
*
* You may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* If any of the files related to licensing are missing or if you have any
* other questions related to licensing please contact Trustwave Holdings, Inc.
* directly using the email address security@modsecurity.org.
*/
#include "modsecurity.h"
#include "re.h"
#include "msc_pcre.h"
#include "msc_geo.h"
#include "msc_gsb.h"
#include "apr_lib.h"
#include "apr_strmatch.h"
#include "acmp.h"
#include "msc_util.h"
#include "msc_tree.h"
#include "msc_crypt.h"
#include "msc_remote_rules.h"
#include "msc_status_engine.h"
#include <apr_sha1.h>
#if APR_HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#ifdef WITH_SSDEEP
#include "fuzzy.h"
#endif
#include "libinjection/libinjection.h"
/**
*
*/
void msre_engine_op_register(msre_engine *engine, const char *name,
fn_op_param_init_t fn1, fn_op_execute_t 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;
}
/* noMatch */
static int msre_op_nomatch_execute(modsec_rec *msr, msre_rule *rule,
msre_var *var, char **error_msg)
{
*error_msg = "No match.";
/* Never match. */
return 0;
}
/* ipmatch */
/**
* \brief Init function to ipmatch operator
*
* \param rule ModSecurity rule struct
* \param error_msg Error message
*
* \retval 1 On Success
* \retval 0 On Fail
*/
static int msre_op_ipmatch_param_init(msre_rule *rule, char **error_msg) {
char *param = NULL;
int res = 0;
if (error_msg == NULL)
return -1;
else
*error_msg = NULL;
param = apr_pstrdup(rule->ruleset->mp, rule->op_param);
res = ip_tree_from_param(rule->ruleset->mp, param, &rule->ip_op,
error_msg);
if (res)
return 0;
return 1;
}
/**
* \brief Execution function to ipmatch operator
*
* \param msr Pointer internal modsec request structure
* \param rule Pointer to the rule
* \param var Pointer to variable structure
* \param error_msg Pointer to error msg
*
* \retval -1 On Failure
* \retval 1 On Match
* \retval 0 On No Match
*/
static int msre_op_ipmatch_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) {
TreeRoot *rtree = NULL;
int res = 0;
if (error_msg == NULL)
return -1;
else
*error_msg = NULL;
if (rule == NULL || rule->ip_op == NULL) {
msr_log(msr, 1, "ipMatch Internal Error: ipmatch value is null.");
return 0;
}
rtree = rule->ip_op;
res = tree_contains_ip(msr->mp, rtree, var->value, NULL, error_msg);
if (res < 0) {
msr_log(msr, 1, "%s", *error_msg);
*error_msg = NULL;
}
if (res > 0) {
*error_msg = apr_psprintf(msr->mp, "IPmatch: \"%s\" matched at %s.", var->value, var->name);
}
return res;
}
/**
* \brief Init function to ipmatchFromFile operator
*
* \param rule Pointer to the rule
* \param error_msg Pointer to error msg
*
* \retval 1 On Success
* \retval 0 On Fail
*/
static int msre_op_ipmatchFromFile_param_init(msre_rule *rule, char **error_msg) {
const char *rootpath = NULL;
const char *filepath = NULL;
const char *ipfile_path = NULL;
char *fn = NULL;
int res = 0;
TreeRoot *rtree = NULL;
if ((rule->op_param == NULL) || (strlen(rule->op_param) == 0))
{
*error_msg = apr_psprintf(rule->ruleset->mp, "Missing parameter for " \
"operator 'ipmatchFromFile'.");
return 0;
}
fn = apr_pstrdup(rule->ruleset->mp, rule->op_param);
while ((apr_isspace(*fn) != 0) && (*fn != '\0'))
{
fn++;
}
if (*fn == '\0') {
*error_msg = apr_psprintf(rule->ruleset->mp, "Empty file specification " \
"for operator ipmatchFromFile \"%s\"", fn);
return 0;
}
filepath = fn;
if (strlen(fn) > strlen("http://") &&
strncmp(fn, "http://", strlen("http://")) == 0)
{
*error_msg = apr_psprintf(rule->ruleset->mp, "HTTPS address or file " \
"path are expected for operator ipmatchFromFile \"%s\"", fn);
return 0;
}
else if (strlen(fn) > strlen("https://") &&
strncmp(fn, "https://", strlen("https://")) == 0)
{
#ifdef WITH_CURL
res = ip_tree_from_uri(&rtree, fn, rule->ruleset->mp, error_msg);
if (res == -2)
{
/* Failed to download but we won't stop the webserver to start. */
return 1;
}
else if (res)
{
return 0;
}
#else
*error_msg = apr_psprintf(rule->ruleset->mp, "ModSecurity was not " \
"compiled with Curl support, it cannot load: \"%s\"", fn);
return 0;
#endif
}
else
{
ipfile_path = apr_pstrndup(rule->ruleset->mp, rule->filename,
strlen(rule->filename) - strlen(apr_filepath_name_get(rule->filename)));
if (apr_filepath_root(&rootpath, &filepath, APR_FILEPATH_TRUENAME,
rule->ruleset->mp) != APR_SUCCESS) {
apr_filepath_merge(&fn, ipfile_path, fn, APR_FILEPATH_TRUENAME, rule->ruleset->mp);
}
res = ip_tree_from_file(&rtree, fn, rule->ruleset->mp, error_msg);
if (res)
{
return 0;
}
}
rule->op_param_data = rtree;
return 1;
}
/**
* \brief Execution function to ipmatchFromFile operator
*
* \param msr Pointer internal modsec request structure
* \param rule Pointer to the rule
* \param var Pointer to variable structure
* \param error_msg Pointer to error msg
*
* \retval -1 On Failure
* \retval 1 On Match
* \retval 0 On No Match
*/
static int msre_op_ipmatchFromFile_execute(modsec_rec *msr, msre_rule *rule,
msre_var *var, char **error_msg) {
TreeRoot *rtree = (TreeRoot *)rule->op_param_data;
int res = 0;
if (error_msg == NULL)
return -1;
else
*error_msg = NULL;
if (rtree == NULL)
{
if (msr->txcfg->debuglog_level >= 6)
{
msr_log(msr, 1, "ipMatchFromFile: tree value is null.");
}
return 0;
}
if (msr->txcfg->debuglog_level >= 4) {
msr_log(msr, 4, "IPmatchFromFile: Total tree entries: %d, ipv4 %d " \
"ipv6 %d", rtree->ipv4_tree->count+rtree->ipv6_tree->count,
rtree->ipv4_tree->count, rtree->ipv6_tree->count);
}
res = tree_contains_ip(msr->mp, rtree, var->value, msr,
error_msg);
if (res < 0)
msr_log(msr, 9, "%s", *error_msg);
if (res > 0)
*error_msg = apr_psprintf(msr->mp, "IPmatchFromFile: \"%s\" matched at " \
"%s.", var->value, var->name);
return res;
}
/* rsub */
static char *param_remove_escape(msre_rule *rule, char *str, int len) {
char *parm = apr_pcalloc(rule->ruleset->mp, len);
char *ret = parm;
for(;*str!='\0';str++) {
if(*str != '\\') {
*parm++ = *str;
} else {
str++;
if(*str != '/') {
str--;
*parm++ = *str;
} else {
*parm++ = *str;
}
}
}
*parm = '\0';
return ret;
}
/**
* \brief Init function to rsub operator
*
* \param rule Pointer to the rule
* \param error_msg Pointer to error msg
*
* \retval 1 On Success
* \retval 0 On Fail
*/
#if !defined(MSC_TEST)
static int msre_op_rsub_param_init(msre_rule *rule, char **error_msg) {
#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER > 0
ap_regex_t *regex;
#else
regex_t *regex;
#endif
const char *pattern = NULL;
const char *line = NULL;
char *reg_pattern = NULL;
char *replace = NULL;
char *e_pattern = NULL;
char *parsed_replace = NULL;
char *flags = NULL;
char *data = NULL;
char delim;
int ignore_case = 0;
unsigned short int op_len = 0;
if (error_msg == NULL) return -1;
*error_msg = NULL;
line = rule->op_param;
if (apr_tolower(*line) != 's') {
*error_msg = apr_psprintf(rule->ruleset->mp, "Error rsub operator format, must be s/ pattern");
return -1;
}
data = apr_pstrdup(rule->ruleset->mp, line);
delim = *++data;
if (delim)
reg_pattern = ++data;
if (reg_pattern) {
if (*data != delim) {
for(;*data != '\0' ;data++) {
if(*data == delim) {
data--;
if(*data == '\\') {
data++;
continue;
}
break;
}
}
}
if (*data) {
*++data = '\0';
++data;
replace = data;
}
}
if (replace) {
if (*data != delim) {
for(;*data != '\0' ;data++) {
if(*data == delim) {
data--;
if(*data == '\\') {
data++;
continue;
}
break;
}
}
}
if (*data) {
*++data = '\0';
flags = ++data;
}
}
if (!delim || !reg_pattern || !replace) {
*error_msg = apr_psprintf(rule->ruleset->mp, "Error rsub operator format - must be s/regex/str/[flags]");
return -1;
}
op_len = strlen(replace);
parsed_replace = apr_pstrdup(rule->ruleset->mp, parse_pm_content(param_remove_escape(rule, replace, strlen(replace)),
op_len, rule, error_msg));
if(!parsed_replace) {
*error_msg = apr_psprintf(rule->ruleset->mp, "Error rsub operator parsing input data");
return -1;
}
rule->sub_str = apr_pstrmemdup(rule->ruleset->mp, parsed_replace, strlen(parsed_replace));
if (flags) {
while (*flags) {
delim = apr_tolower(*flags);
if (delim == 'i')
ignore_case = 1;
else if (delim == 'd')
rule->escape_re = 1;
else
*error_msg = apr_psprintf(rule->ruleset->mp, "Regex flag not supported");
flags++;
}
}
e_pattern = param_remove_escape(rule, reg_pattern, strlen(reg_pattern));
pattern = apr_pstrndup(rule->ruleset->mp, e_pattern, strlen(e_pattern));
if(strstr(pattern,"%{") == NULL) {
#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER > 0
regex = ap_pregcomp(rule->ruleset->mp, pattern, AP_REG_EXTENDED |
(ignore_case ? AP_REG_ICASE : 0));
#else
regex = ap_pregcomp(rule->ruleset->mp, pattern, REG_EXTENDED |
(ignore_case ? REG_ICASE : 0));
#endif
rule->sub_regex = regex;
} else {
rule->re_precomp = 1;
rule->re_str = apr_pstrndup(rule->ruleset->mp, pattern, strlen(pattern));
rule->sub_regex = NULL;
}
return 1; /* OK */
}
/**
* \brief Execution function to rsub operator
*
* \param msr Pointer internal modsec request structure
* \param rule Pointer to the rule
* \param var Pointer to variable structure
* \param error_msg Pointer to error msg
*
* \retval -1 On Failure
* \retval 1 On Match
* \retval 0 On No Match
*/
static int msre_op_rsub_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));
msc_string *re_pattern = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
char *offset = NULL;
char *data = NULL, *pattern = NULL;
char *data_out = NULL;
unsigned int size = 0;
unsigned int maxsize=0;
int output_body = 0, input_body = 0, sl;
#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER > 0
ap_regmatch_t pmatch[AP_MAX_REG_MATCH];
#else
regmatch_t pmatch[AP_MAX_REG_MATCH];
#endif
if (error_msg == NULL) return -1;
*error_msg = NULL;
if(strcmp(var->name,"STREAM_OUTPUT_BODY") == 0 ) {
output_body = 1;
} else if(strcmp(var->name,"STREAM_INPUT_BODY") == 0 ) {
input_body = 1;
} else {
msr_log(msr,9,"Operator rsub only works with STREAM_* variables");
return -1;
}
if(rule->re_precomp == 1) {
re_pattern->value = apr_pstrndup(msr->mp, rule->re_str, strlen(rule->re_str));
re_pattern->value_len = strlen(re_pattern->value);
expand_macros(msr, re_pattern, rule, msr->mp);
if(strlen(re_pattern->value) > 0) {
if(rule->escape_re == 1) {
pattern = log_escape_re(msr->mp, re_pattern->value);
if (msr->txcfg->debuglog_level >= 6) {
msr_log(msr, 6, "Escaping pattern [%s]",pattern);
}
#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER > 0
rule->sub_regex = ap_pregcomp(msr->mp, pattern, AP_REG_EXTENDED);
#else
rule->sub_regex = ap_pregcomp(msr->mp, pattern, REG_EXTENDED);
#endif
} else {
#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER > 0
rule->sub_regex = ap_pregcomp(msr->mp, re_pattern->value, AP_REG_EXTENDED);
#else
rule->sub_regex = ap_pregcomp(msr->mp, re_pattern->value, REG_EXTENDED);
#endif
}
} else {
rule->sub_regex = NULL;
}
}
if(rule->sub_regex == NULL) {
*error_msg = "Internal Error: regex data is null.";
return -1;
}
str->value = apr_pstrndup(msr->mp, rule->sub_str, strlen(rule->sub_str));
str->value_len = strlen(str->value);
if(strstr(rule->sub_str,"%{") != NULL)
expand_macros(msr, str, rule, msr->mp);
maxsize=var->value_len+(AP_MAX_REG_MATCH*1024)+1;
nextround:
data = apr_pcalloc(msr->mp, maxsize+1);
if(data == NULL) {
*error_msg = "Internal Error: cannot allocate memory";
return -1;
}
data_out=data;
size=0;
for (offset = (char*)var->value; !ap_regexec(rule->sub_regex, offset, AP_MAX_REG_MATCH, pmatch, 0); ) {
//Copy of data before the regex match
int i;
int s = pmatch [0].rm_so;
int p_len=pmatch [0].rm_eo - pmatch [0].rm_so;
if (size+s>maxsize) {
maxsize*=2;
goto nextround;
}
memcpy(data_out,offset,s);
data_out+=s;
size+=s;
//Copy of regex match with replacing data \1..\9
for(i=0;i<str->value_len;) {
char *x = str->value+i;
if (*x == '\\' && *(x + 1) > '0' && *(x + 1) <= '9') {
int capture=*(x + 1) - 48;
int capture_len=pmatch[capture].rm_eo-pmatch[capture].rm_so;
if (size+capture_len>maxsize)
{
maxsize*=2;
goto nextround;
}
memcpy(data_out,offset+pmatch[capture].rm_so,capture_len);
data_out+= capture_len;
size+=capture_len;
i+=2;
} else {
if (size+1>maxsize) {
maxsize*=2;
goto nextround;
}
*data_out=*(str->value+i);
data_out++;
size++;
i++;
}
}
offset+=s;
offset+=p_len;
}
//Copy of data after the last regex match
sl = strlen(offset);
if (size+sl>maxsize) {
maxsize*=2;
goto nextround;
}
memcpy(data_out,offset,sl);
data_out+=sl;
size+=sl;
*data_out=0;
if(msr->stream_output_data != NULL && output_body == 1) {
memset(msr->stream_output_data, 0x0, msr->stream_output_length);
free(msr->stream_output_data);
msr->stream_output_data = NULL;
msr->stream_output_length = 0;
msr->stream_output_data = (char *)malloc(size+1);
if(msr->stream_output_data == NULL) {
return -1;
}
msr->stream_output_length = size;
memset(msr->stream_output_data, 0x0, size+1);
msr->of_stream_changed = 1;
memcpy(msr->stream_output_data, data, size);
msr->stream_output_data[size] = '\0';
var->value_len = size;
var->value = msr->stream_output_data;
}
if(msr->stream_input_data != NULL && input_body == 1) {
memset(msr->stream_input_data, 0x0, msr->stream_input_length);
free(msr->stream_input_data);
msr->stream_input_data = NULL;
msr->stream_input_length = 0;
msr->stream_input_data = (char *)malloc(size+1);
if(msr->stream_input_data == NULL) {
return -1;
}
msr->stream_input_length = size;
memset(msr->stream_input_data, 0x0, size+1);
msr->if_stream_changed = 1;
memcpy(msr->stream_input_data, data, size);
msr->stream_input_data[size] = '\0';
var->value_len = size;
var->value = msr->stream_input_data;
}
if (! *error_msg) {
*error_msg = apr_psprintf(msr->mp, "Operator rsub succeeded.");
}
return 1;
}
#endif /* MSC_TEST */
/**
* \brief Init function to validateHash
*
* \param rule ModSecurity rule struct
* \param error_msg Error message
*
* \retval 1 On success
* \retval 0 On fail
*/
static int msre_op_validateHash_param_init(msre_rule *rule, char **error_msg) {
const char *errptr = NULL;
int erroffset;
msc_regex_t *regex;
const char *pattern = rule->op_param;
#ifdef WITH_PCRE_STUDY
#ifdef WITH_PCRE_JIT
int rc, jit;
#endif
#endif
if (error_msg == NULL) return -1;
*error_msg = NULL;
/* Compile pattern */
if(strstr(pattern,"%{") == NULL) {
regex = msc_pregcomp_ex(rule->ruleset->mp, pattern, PCRE_DOTALL | PCRE_DOLLAR_ENDONLY, &errptr, &erroffset, msc_pcre_match_limit, msc_pcre_match_limit_recursion);
if (regex == NULL) {
*error_msg = apr_psprintf(rule->ruleset->mp, "Error compiling pattern (offset %d): %s",
erroffset, errptr);
return 0;
}
#ifdef WITH_PCRE_STUDY
#ifdef WITH_PCRE_JIT
rc = msc_fullinfo(regex, PCRE_INFO_JIT, &jit);
if ((rc != 0) || (jit != 1)) {
*error_msg = apr_psprintf(rule->ruleset->mp,
"Rule %pp [id \"%s\"][file \"%s\"][line \"%d\"] - "
"Execution error - "
"Does not support JIT (%d)",
rule,((rule->actionset != NULL)&&((rule->actionset->id != NULL)&&
(rule->actionset->id != NOT_SET_P))) ? rule->actionset->id : "-",
rule->filename != NULL ? rule->filename : "-",
rule->line_num,rc);
}
#endif
#endif
rule->op_param_data = regex;
} else {
rule->re_precomp = 1;
rule->re_str = apr_pstrndup(rule->ruleset->mp, pattern, strlen(pattern));
rule->op_param_data = NULL;
}
return 1; /* OK */
}
/**
* \brief Execute function to validateHash
*
* \param msr ModSecurity transaction resource
* \param rule ModSecurity rule struct
* \param var ModSecurity variable struct
* \param error_msg Error message
*
* \retval 1 On success
* \retval 0 On fail
*/
static int msre_op_validateHash_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) {
msc_regex_t *regex = (msc_regex_t *)rule->op_param_data;
msc_string *re_pattern = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
const char *target;
const char *errptr = NULL;
int erroffset;
unsigned int target_length;
char *my_error_msg = NULL;
int ovector[33];
int rc;
const char *pattern = NULL;
#ifdef WITH_PCRE_STUDY
#ifdef WITH_PCRE_JIT
int jit;
#endif
#endif
if (error_msg == NULL) return -1;
*error_msg = NULL;
if (msr->txcfg->hash_enforcement == HASH_DISABLED || msr->txcfg->hash_is_enabled == HASH_DISABLED)
return 0;
if (regex == NULL) {
if(rule->re_precomp == 0) {
*error_msg = "Internal Error: regex data is null.";
return -1;
} else {
if(re_pattern == NULL) {
*error_msg = "Internal Error: regex variable data is null.";
return -1;
}
re_pattern->value = apr_pstrndup(msr->mp, rule->re_str, strlen(rule->re_str));
re_pattern->value_len = strlen(re_pattern->value);
expand_macros(msr, re_pattern, rule, msr->mp);
pattern = log_escape_re(msr->mp, re_pattern->value);
if (msr->txcfg->debuglog_level >= 6) {
msr_log(msr, 6, "Escaping pattern [%s]",pattern);
}
regex = msc_pregcomp_ex(rule->ruleset->mp, pattern, PCRE_DOTALL | PCRE_DOLLAR_ENDONLY, &errptr,
&erroffset, msc_pcre_match_limit, msc_pcre_match_limit_recursion);
if (regex == NULL) {
*error_msg = apr_psprintf(rule->ruleset->mp, "Error compiling pattern (offset %d): %s",
erroffset, errptr);
return 0;
}
#ifdef WITH_PCRE_STUDY
#ifdef WITH_PCRE_JIT
if (msr->txcfg->debuglog_level >= 4) {
rc = msc_fullinfo(regex, PCRE_INFO_JIT, &jit);
if ((rc != 0) || (jit != 1)) {
*error_msg = apr_psprintf(rule->ruleset->mp,
"Rule %pp [id \"%s\"][file \"%s\"][line \"%d\"] - "
"Execution error - "
"Does not support JIT (%d)",
rule,((rule->actionset != NULL)&&((rule->actionset->id != NULL)&&
(rule->actionset->id != NOT_SET_P))) ? rule->actionset->id : "-",
rule->filename != NULL ? rule->filename : "-",
rule->line_num,rc);
msr_log(msr, 4, "%s.", *error_msg);
}
}
#endif
#endif
}
}
/* If the given target is null run against an empty
* string. This is a behaviour consistent with previous
* releases.
*/
if (var->value == NULL) {
target = "";
target_length = 0;
} else {
target = var->value;
target_length = var->value_len;
}
/* We always use capture so that ovector can be used as working space
* and no memory has to be allocated for any backreferences.
*/
rc = msc_regexec_capture(regex, target, target_length, ovector, 30, &my_error_msg);
if ((rc == PCRE_ERROR_MATCHLIMIT) || (rc == PCRE_ERROR_RECURSIONLIMIT)) {
msc_string *s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
if (s == NULL) return -1;
s->name = apr_pstrdup(msr->mp, "MSC_PCRE_LIMITS_EXCEEDED");
if (s->name == NULL) return -1;
s->name_len = strlen(s->name);
s->value = apr_pstrdup(msr->mp, "1");
if (s->value == NULL) return -1;
s->value_len = 1;
apr_table_setn(msr->tx_vars, s->name, (void *)s);
*error_msg = apr_psprintf(msr->mp,
"Rule %pp [id \"%s\"][file \"%s\"][line \"%d\"] - "
"Execution error - "
"PCRE limits exceeded (%d): %s",
rule,((rule->actionset != NULL)&&(rule->actionset->id != NULL)) ? rule->actionset->id : "-",
rule->filename != NULL ? rule->filename : "-",
rule->line_num,rc, my_error_msg);
msr_log(msr, 3, "%s.", *error_msg);
return 0; /* No match. */
}
else if (rc < -1) {
*error_msg = apr_psprintf(msr->mp, "Regex execution failed (%d): %s",
rc, my_error_msg);
return -1;
}
if (rc != PCRE_ERROR_NOMATCH) { /* Match. */
/* We no longer escape the pattern here as it is done when logging */
char *pattern = apr_pstrdup(msr->mp, log_escape(msr->mp, regex->pattern ? regex->pattern : "<Unknown Match>"));
char *hmac = NULL, *valid = NULL;
char *hash_link = NULL, *nlink = NULL;
if (strlen(pattern) > 252) {
*error_msg = apr_psprintf(msr->mp, "Request URI matched \"%.252s ...\" at %s.",
pattern, var->name);
} else {
*error_msg = apr_psprintf(msr->mp, "Request URI matched \"%s\" at %s.",
pattern, var->name);
}
valid = strstr(target, msr->txcfg->crypto_param_name);
if(valid == NULL) {
if (msr->txcfg->debuglog_level >= 9)
msr_log(msr, 9, "Request URI without hash parameter [%s]", target);
if (strlen(pattern) > 252) {
*error_msg = apr_psprintf(msr->mp, "Request URI matched \"%.252s ...\" at %s. No Hash parameter",
pattern, var->name);
} else {
*error_msg = apr_psprintf(msr->mp, "Request URI matched \"%s\" at %s. No Hash parameter",
pattern, var->name);
}
return 1;
} else {
if(strlen(valid) < strlen(msr->txcfg->crypto_param_name)+1)
return 1;
hmac = valid+strlen(msr->txcfg->crypto_param_name)+1;
nlink = apr_pstrmemdup(msr->mp, target, strlen(target) - strlen(valid) - 1);
msr_log(msr, 9, "Validating URI %s size %zu",nlink,strlen(nlink));
hash_link = do_hash_link(msr, (char *)nlink, HASH_ONLY);
if(strcmp(hmac, hash_link) != 0) {
if (strlen(pattern) > 252) {
*error_msg = apr_psprintf(msr->mp, "Request URI matched \"%.252s ...\" at %s. Hash parameter hash value = [%s] Requested URI hash value = [%s]",
pattern, var->name, hmac, hash_link);
} else {
*error_msg = apr_psprintf(msr->mp, "Request URI matched \"%s\" at %s. Hash parameter hash value = [%s] Requested URI hash value = [%s]",
pattern, var->name, hmac, hash_link);
}
return 1;
}
}
return 0;
}
return 0;
}
/* rx */
static int msre_op_rx_param_init(msre_rule *rule, char **error_msg) {
const char *errptr = NULL;
int erroffset;
msc_regex_t *regex;
const char *pattern = rule->op_param;
#ifdef WITH_PCRE_STUDY
#ifdef WITH_PCRE_JIT
int rc, jit;
#endif
#endif
if (error_msg == NULL) return -1;
*error_msg = NULL;
/* Compile pattern */
if(strstr(pattern,"%{") == NULL) {
regex = msc_pregcomp_ex(rule->ruleset->mp, pattern, PCRE_DOTALL | PCRE_DOLLAR_ENDONLY, &errptr, &erroffset, msc_pcre_match_limit, msc_pcre_match_limit_recursion);
if (regex == NULL) {
*error_msg = apr_psprintf(rule->ruleset->mp, "Error compiling pattern (offset %d): %s",
erroffset, errptr);
return 0;
}
#ifdef WITH_PCRE_STUDY
#ifdef WITH_PCRE_JIT
rc = msc_fullinfo(regex, PCRE_INFO_JIT, &jit);
if ((rc != 0) || (jit != 1)) {
*error_msg = apr_psprintf(rule->ruleset->mp,
"Rule %pp [id \"%s\"][file \"%s\"][line \"%d\"] - "
"Execution error - "
"Does not support JIT (%d)",
rule,((rule->actionset != NULL)&&((rule->actionset->id != NULL)&&
(rule->actionset->id != NOT_SET_P))) ? rule->actionset->id : "-",
rule->filename != NULL ? rule->filename : "-",
rule->line_num,rc);
}
#endif
#endif
rule->op_param_data = regex;
} else {
rule->re_precomp = 1;
rule->re_str = apr_pstrndup(rule->ruleset->mp, pattern, strlen(pattern));
rule->op_param_data = NULL;
}
return 1; /* OK */
}
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;
msc_string *re_pattern = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
const char *target;
const char *errptr = NULL;
int erroffset;
unsigned int target_length;
char *my_error_msg = NULL;
int ovector[33];
int capture = 0;
int matched_bytes = 0;
int matched = 0;
int rc;
char *qspos = NULL;
const char *parm = NULL, *pattern = NULL;
msc_parm *mparm = NULL;
#ifdef WITH_PCRE_STUDY
#ifdef WITH_PCRE_JIT
int jit;
#endif
#endif
if (error_msg == NULL) return -1;
*error_msg = NULL;
if (regex == NULL) {
if(rule->re_precomp == 0) {
*error_msg = "Internal Error: regex data is null.";
return -1;
} else {
if(re_pattern == NULL) {
*error_msg = "Internal Error: regex variable data is null.";
return -1;
}
re_pattern->value = apr_pstrndup(msr->mp, rule->re_str, strlen(rule->re_str));
re_pattern->value_len = strlen(re_pattern->value);
expand_macros(msr, re_pattern, rule, msr->mp);
pattern = log_escape_re(msr->mp, re_pattern->value);
if (msr->txcfg->debuglog_level >= 6) {
msr_log(msr, 6, "Escaping pattern [%s]",pattern);
}
regex = msc_pregcomp_ex(rule->ruleset->mp, pattern, PCRE_DOTALL | PCRE_DOLLAR_ENDONLY, &errptr, &erroffset, msc_pcre_match_limit, msc_pcre_match_limit_recursion);
if (regex == NULL) {
*error_msg = apr_psprintf(rule->ruleset->mp, "Error compiling pattern (offset %d): %s",
erroffset, errptr);
return 0;
}
#ifdef WITH_PCRE_STUDY
#ifdef WITH_PCRE_JIT
if (msr->txcfg->debuglog_level >= 4) {
rc = msc_fullinfo(regex, PCRE_INFO_JIT, &jit);
if ((rc != 0) || (jit != 1)) {
*error_msg = apr_psprintf(rule->ruleset->mp,
"Rule %pp [id \"%s\"][file \"%s\"][line \"%d\"] - "
"Execution error - "
"Does not support JIT (%d)",
rule,((rule->actionset != NULL)&&((rule->actionset->id != NULL)&&
(rule->actionset->id != NOT_SET_P))) ? rule->actionset->id : "-",
rule->filename != NULL ? rule->filename : "-",
rule->line_num,rc);
msr_log(msr, 4, "%s.", *error_msg);
}
}
#endif
#endif
}
}
/* If the given target is null run against an empty
* string. This is a behaviour consistent with previous
* releases.
*/
if (var->value == NULL) {
target = "";
target_length = 0;
} else {
target = var->value;
target_length = var->value_len;
}
/* Are we supposed to capture subexpressions? */
capture = apr_table_get(rule->actionset->actions, "capture") ? 1 : 0;
matched_bytes = apr_table_get(rule->actionset->actions, "sanitizeMatchedBytes") ? 1 : 0;
if(!matched_bytes)
matched_bytes = apr_table_get(rule->actionset->actions, "sanitiseMatchedBytes") ? 1 : 0;
matched = apr_table_get(rule->actionset->actions, "sanitizeMatched") ? 1 : 0;
if(!matched)
matched = apr_table_get(rule->actionset->actions, "sanitiseMatched") ? 1 : 0;
/* Show when the regex captures but "capture" is not set */
if (msr->txcfg->debuglog_level >= 6) {
int capcount = 0;
rc = msc_fullinfo(regex, PCRE_INFO_CAPTURECOUNT, &capcount);
if (msr->txcfg->debuglog_level >= 6) {
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 == PCRE_ERROR_MATCHLIMIT) || (rc == PCRE_ERROR_RECURSIONLIMIT)) {
msc_string *s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
if (s == NULL) return -1;
s->name = apr_pstrdup(msr->mp, "MSC_PCRE_LIMITS_EXCEEDED");
if (s->name == NULL) return -1;
s->name_len = strlen(s->name);
s->value = apr_pstrdup(msr->mp, "1");
if (s->value == NULL) return -1;
s->value_len = 1;
apr_table_setn(msr->tx_vars, s->name, (void *)s);
*error_msg = apr_psprintf(msr->mp,
"Rule %pp [id \"%s\"][file \"%s\"][line \"%d\"] - "
"Execution error - "
"PCRE limits exceeded (%d): %s",
rule,((rule->actionset != NULL)&&(rule->actionset->id != NULL)) ? rule->actionset->id : "-",
rule->filename != NULL ? rule->filename : "-",
rule->line_num,rc, my_error_msg);
msr_log(msr, 3, "%s.", *error_msg);
return 0; /* No match. */
}
else if (rc < -1) {
*error_msg = apr_psprintf(msr->mp, "Regex execution failed (%d): %s",
rc, my_error_msg);
return -1;
}
/* Handle captured subexpressions. */
if (capture && rc > 0) {
int i;
/* Unset any of the previously set capture variables. */
apr_table_unset(msr->tx_vars, "0");
apr_table_unset(msr->tx_vars, "1");
apr_table_unset(msr->tx_vars, "2");
apr_table_unset(msr->tx_vars, "3");
apr_table_unset(msr->tx_vars, "4");
apr_table_unset(msr->tx_vars, "5");
apr_table_unset(msr->tx_vars, "6");
apr_table_unset(msr->tx_vars, "7");
apr_table_unset(msr->tx_vars, "8");
apr_table_unset(msr->tx_vars, "9");
/* 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, "%d", i);
if (s->name == NULL) return -1;
s->name_len = strlen(s->name);
s->value = apr_pstrmemdup(msr->mp,
target + ovector[2 * i], ovector[2 * i + 1] - ovector[2 * i]);
if (s->value == NULL) return -1;
s->value_len = (ovector[2 * i + 1] - ovector[2 * i]);
apr_table_addn(msr->tx_vars, s->name, (void *)s);
if(((matched == 1) || (matched_bytes == 1)) && (var != NULL) && (var->name != NULL)) {
qspos = apr_psprintf(msr->mp, "%s", var->name);
parm = strstr(qspos, ":");
if (parm != NULL) {
parm++;
mparm = apr_palloc(msr->mp, sizeof(msc_parm));
if (mparm == NULL)
continue;
mparm->value = apr_pstrmemdup(msr->mp,s->value,s->value_len);
mparm->pad_1 = rule->actionset->arg_min;
mparm->pad_2 = rule->actionset->arg_max;
apr_table_addn(msr->pattern_to_sanitize, parm, (void *)mparm);
} else {
mparm = apr_palloc(msr->mp, sizeof(msc_parm));
if (mparm == NULL)
continue;
mparm->value = apr_pstrmemdup(msr->mp,s->value,s->value_len);
apr_table_addn(msr->pattern_to_sanitize, qspos, (void *)mparm);
}
}
if (msr->txcfg->debuglog_level >= 9) {
msr_log(msr, 9, "Added regex subexpression to TX.%d: %s", i,
log_escape_nq_ex(msr->mp, s->value, s->value_len));
}
}
}
if (rc != PCRE_ERROR_NOMATCH) { /* Match. */
/* We no longer escape the pattern here as it is done when logging */
char *pattern = apr_pstrdup(msr->mp, log_escape(msr->mp, regex->pattern ? regex->pattern : "<Unknown Match>"));
/* This message will be logged. */
if (strlen(pattern) > 252) {
*error_msg = apr_psprintf(msr->mp, "Pattern match \"%.252s ...\" at %s.",
pattern, var->name);
} else {
*error_msg = apr_psprintf(msr->mp, "Pattern match \"%s\" at %s.",
pattern, var->name);
}
return 1;
}
/* No match. */
return 0;
}
/* pm */
static int msre_op_pm_param_init(msre_rule *rule, char **error_msg) {
ACMP *p;
const char *phrase;
const char *next;
unsigned short int op_len;
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 */
}
op_len = strlen(rule->op_param);
p = acmp_create(0, rule->ruleset->mp);
if (p == NULL) return 0;
phrase = apr_pstrdup(rule->ruleset->mp, parse_pm_content(rule->op_param, op_len, rule, error_msg));
if(phrase == NULL)
phrase = apr_pstrdup(rule->ruleset->mp, rule->op_param);
/* Loop through phrases */
/* ENH: Need to allow quoted phrases w/space */
for (;;) {
while((apr_isspace(*phrase) != 0) && (*phrase != '\0')) phrase++;
if (*phrase == '\0') break;
next = phrase;
while((apr_isspace(*next) == 0) && (*next != 0)) next++;
acmp_add_pattern(p, phrase, NULL, NULL, next - phrase);
phrase = next;
}
acmp_prepare(p);
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 = NULL;
char *next = NULL;
char *start = NULL;
char *end = NULL;
const char *rulefile_path;
char *processed = NULL;
unsigned short int op_len;
apr_status_t rc;
apr_file_t *fd = NULL;
ACMP *p;
if ((rule->op_param == NULL)||(strlen(rule->op_param) == 0)) {
*error_msg = apr_psprintf(rule->ruleset->mp, "Missing parameter for operator 'pmFromFile'.");
return 0; /* ERROR */
}
p = acmp_create(0, rule->ruleset->mp);
if (p == NULL) return 0;
fn = apr_pstrdup(rule->ruleset->mp, 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((apr_isspace(*fn) != 0) && (*fn != '\0')) fn++;
if (*fn == '\0') break;
next = fn;
while((apr_isspace(*next) == 0) && (*next != '\0')) next++;
while((apr_isspace(*next) != 0) && (*next != '\0')) *(next++) = '\0';
/* Add path of the rule filename for a relative phrase filename */
filepath = fn;
if (strlen(fn) > strlen("http://") &&
strncmp(fn, "http://", strlen("http://")) == 0)
{
*error_msg = apr_psprintf(rule->ruleset->mp, "HTTPS address or " \
"file path are expected for operator pmFromFile \"%s\"", fn);
return 0;
}
else if (strlen(fn) > strlen("https://") &&
strncmp(fn, "https://", strlen("https://")) == 0)
{
#ifdef WITH_CURL
int res = 0;
char *word = NULL;
char *brkt = NULL;
char *sep = "\n";
struct msc_curl_memory_buffer_t chunk;
res = msc_remote_download_content(rule->ruleset->mp, fn, NULL,
&chunk, error_msg);
if (res == -2)
{
/* If download failed but SecRemoteRulesFailAction is set to Warn. */
return 1;
}
else if (res < 0)
{
return 0;
}
for (word = strtok_r(chunk.memory, sep, &brkt);
word;
word = strtok_r(NULL, sep, &brkt))
{
/* Ignore empty lines and comments */
if (*word == '#') continue;
acmp_add_pattern(p, word, NULL, NULL, strlen(word));
}
msc_remote_clean_chunk(&chunk);
#else
*error_msg = apr_psprintf(rule->ruleset->mp, "ModSecurity was not " \
"compiled with Curl support, it cannot load: \"%s\"", fn);
return 0;
#endif
}
else
{
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_BUFFERED | 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 not read \"%s\" line %d: %s", fn, line, apr_strerror(rc, errstr, 1024));
return 0;
}
op_len = strlen(buf);
processed = apr_pstrdup(rule->ruleset->mp, parse_pm_content(buf, op_len, rule, error_msg));
/* Trim Whitespace */
if(processed != NULL)
start = processed;
else
start = buf;
while ((apr_isspace(*start) != 0) && (*start != '\0')) start++;
if(processed != NULL)
end = processed + strlen(processed);
else
end = buf + strlen(buf);
if (end > start) end--;
while ((end > start) && (apr_isspace(*end) != 0)) end--;
if (end > start) {
*(++end) = '\0';
}
/* Ignore empty lines and comments */
if ((start == end) || (*start == '#')) continue;
acmp_add_pattern(p, start, NULL, NULL, (end - start));
}
}
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;
int capture;
ACMPT pt;
/* Nothing to read */
if ((var->value == NULL) || (var->value_len == 0)) return 0;
/* Are we supposed to capture subexpressions? */
capture = apr_table_get(rule->actionset->actions, "capture") ? 1 : 0;
if (rule->op_param_data == NULL)
{
if (msr->txcfg->debuglog_level >= 6)
{
msr_log(msr, 1, "ACMPTree is null.");
}
return 0;
}
pt.parser = (ACMP *)rule->op_param_data;
pt.ptr = NULL;
rc = acmp_process_quick(&pt, &match, var->value, var->value_len);
if (rc) {
char *match_escaped = log_escape(msr->mp, match ? match : "<Unknown 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);
}
/* Handle capture as tx.0=match */
if (capture) {
int i;
msc_string *s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
if (s == NULL) return -1;
s->name = "0";
s->name_len = strlen(s->name);
s->value = apr_pstrdup(msr->mp, match);
if (s->value == NULL) return -1;
s->value_len = strlen(s->value);
apr_table_setn(msr->tx_vars, s->name, (void *)s);
if (msr->txcfg->debuglog_level >= 9) {
msr_log(msr, 9, "Added phrase match to TX.0: %s",
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[2];
apr_snprintf(buf, sizeof(buf), "%d", i);
apr_table_unset(msr->tx_vars, buf);
}
}
return 1;
}
return rc;
}
/* gsbLookup */
/**
* \brief Reduce /./ to /
*
* \param pool Pointer to the memory pool
* \param domain Input data
*
* \retval domain On Failure
* \retval url On Success
*/
static const char *gsb_replace_tpath(apr_pool_t *pool, const char *domain, int len) {
char *pos = NULL, *data = NULL;
char *url = NULL;
int match = 0;
url = apr_palloc(pool, len + 1);
data = apr_palloc(pool, len + 1);
memset(data, 0, len+1);
memset(url, 0, len+1);
memcpy(url, domain, len);
while(( pos = strstr(url , "/./" )) != NULL) {
match = 1;
data[0] = '\0';
strncat(data, url, pos - url);
strcat(data , "/");
strcat(data ,pos + strlen("/./"));
strncpy(url , data, len);
}
if(match == 0)
return domain;
return url;
}
/**
* \brief Reduce double dot to single dot
*
* \param msr ModSecurity transation resource
* \param domain Input data
*
* \retval domain On Failure
* \retval reduced On Success
*/
static const char *gsb_reduce_char(apr_pool_t *pool, const char *domain) {
char *ptr = apr_pstrdup(pool, domain);
char *data = NULL;
char *reduced = NULL;
int skip = 0;
if(ptr == NULL)
return domain;
data = apr_pcalloc(pool, strlen(ptr));
if(data == NULL)
return domain;
reduced = data;
while(*ptr != '\0') {
switch(*ptr) {
case '.':
ptr++;
if(*ptr == '.')
skip = 1;
ptr--;
break;
case '/':
ptr++;
if(*ptr == '/')
skip = 1;
ptr--;
break;
}
if(skip == 0) {
*data = *ptr;
data++;
}
ptr++;
skip = 0;
}
*data = '\0'; --data;
if(*data == '.')
*data = '\0';
else
++data;
return reduced;
}
/**
* \brief Verify function to gsbLookup operator
*
* \param msr ModSecurity transaction resource
* \param match Pointer to input data
* \param match_length Input size
*
* \retval -1 On Failure
* \retval 1 On Match
* \retval 0 On No Match
*/
static int verify_gsb(gsb_db *gsb, modsec_rec *msr, const char *match, unsigned int match_length) {
apr_md5_ctx_t ctx;
apr_status_t rc;
unsigned char digest[APR_MD5_DIGESTSIZE];
const char *hash = NULL;
const char *search = NULL;
memset(digest, 0, sizeof(digest));
apr_md5_init(&ctx);
if ((rc = apr_md5_update(&ctx, match, match_length)) != APR_SUCCESS)
return -1;
apr_md5_final(digest, &ctx);
hash = apr_psprintf(msr->mp, "%s", bytes2hex(msr->mp, digest, 16));
if ((hash != NULL) && (gsb->gsb_table != NULL)) {
search = apr_hash_get(gsb->gsb_table, hash, APR_HASH_KEY_STRING);
if (search != NULL)
return 1;
}
return 0;
}
/**
* \brief Init function to gsbLookup operator
*
* \param rule ModSecurity rule struct
* \param error_msg Error message
*
* \retval 1 On Success
* \retval 0 On Fail
*/
static int msre_op_gsbLookup_param_init(msre_rule *rule, char **error_msg) {
const char *errptr = NULL;
int erroffset;
msc_regex_t *regex;
if (error_msg == NULL) return -1;
*error_msg = NULL;
/* Compile rule->op_param */
regex = msc_pregcomp_ex(rule->ruleset->mp, rule->op_param, PCRE_DOTALL | PCRE_MULTILINE, &errptr, &erroffset, msc_pcre_match_limit, msc_pcre_match_limit_recursion);
if (regex == NULL) {
*error_msg = apr_psprintf(rule->ruleset->mp, "Error compiling pattern (offset %d): %s",
erroffset, errptr);
return 0;
}
rule->op_param_data = regex;
return 1; /* OK */
}
/**
* \brief Execution function to gsbLookup operator
*
* \param msr ModSecurity transaction resource
* \param rule ModSecurity rule struct
* \param var ModSecurity variable struct
* \param error_msg Error message
*
* \retval -1 On Failure
* \retval 1 On Match
* \retval 0 On No Match
*/
static int msre_op_gsbLookup_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, char **error_msg) {
msc_regex_t *regex = (msc_regex_t *)rule->op_param_data;
char *my_error_msg = NULL;
int ovector[33];
unsigned int offset = 0;
gsb_db *gsb = msr->txcfg->gsb;
const char *match = NULL;
unsigned int match_length;
unsigned int canon_length;
int rv, i, ret, count_slash;
unsigned int j = 0;
unsigned int size = var->value_len;
char *base = NULL, *domain = NULL, *savedptr = NULL;
char *str = NULL, *canon = NULL, *dot = NULL;
char *data = NULL, *ptr = NULL, *url = NULL;
int capture, domain_len;
int d_pos = -1;
int s_pos = -1;
if (error_msg == NULL) return -1;
*error_msg = NULL;
if(regex == NULL) {
*error_msg = "Internal Error: regex is null.";
return 0;
}
if(gsb == NULL) {
msr_log(msr, 1, "GSB lookup failed without a database. Set SecGsbLookupDB.");
return 0;
}
data = apr_pcalloc(rule->ruleset->mp, var->value_len+1);
if(data == NULL) {
*error_msg = "Internal Error: cannot allocate memory for data.";
return -1;
}
capture = apr_table_get(rule->actionset->actions, "capture") ? 1 : 0;
memcpy(data,var->value,var->value_len);
while (offset < size && (rv = msc_regexec_ex(regex, data, size, offset, PCRE_NOTEMPTY, ovector, 30, &my_error_msg)) >= 0)
{
for(i = 0; i < rv; ++i)
{
match = apr_psprintf(rule->ruleset->mp, "%.*s", ovector[2*i+1] - ovector[2*i], data + ovector[2*i]);
if (match == NULL) {
*error_msg = "Internal Error: cannot allocate memory for match.";
return -1;
}
match = remove_escape(rule->ruleset->mp, match, strlen(match));
match = gsb_replace_tpath(rule->ruleset->mp, match, strlen(match));
match = gsb_reduce_char(rule->ruleset->mp, match);
match_length = strlen(match);
strtolower_inplace((unsigned char *)match);
if((strstr(match,"http") == NULL) && (match_length > 0) && (strchr(match,'.'))) {
/* full url */
if (msr->txcfg->debuglog_level >= 4) {
msr_log(msr, 4, "GSB: Successfully extracted url: %s", match);
}
ret = verify_gsb(gsb, msr, match, match_length);
if(ret > 0) {
set_match_to_tx(msr, capture, match, 0);
if (! *error_msg) {
*error_msg = apr_psprintf(msr->mp, "Gsb lookup for \"%s\" succeeded.",
log_escape_nq(msr->mp, match));
}
str = apr_pstrdup(rule->ruleset->mp,match);
base = apr_strtok(str,"/",&savedptr);
if(base != NULL)
set_match_to_tx(msr, capture, base, 1);
return 1;
}
/* append / in the end of full url */
if ((match[match_length -1] != '/') && (strchr(match,'?') == NULL)) {
canon = apr_psprintf(rule->ruleset->mp, "%s/", match);
if (canon != NULL) {
canon_length = strlen(canon);
ret = verify_gsb(gsb, msr, canon, canon_length);
if(ret > 0) {
set_match_to_tx(msr, capture, match, 0);
if (! *error_msg) {
*error_msg = apr_psprintf(msr->mp, "Gsb lookup for \"%s\" succeeded.",
log_escape_nq(msr->mp, canon));
}
str = apr_pstrdup(rule->ruleset->mp,match);
base = apr_strtok(str,"/",&savedptr);
if(base != NULL)
set_match_to_tx(msr, capture, base, 1);
return 1;
}
}
}
/* Parsing full url */
domain = apr_pstrdup(rule->ruleset->mp, match);
domain_len = strlen(domain);
if(*domain != '/') {
if(domain[domain_len-1] == '.')
domain[domain_len-1] = '\0';
if(domain[domain_len-1] == '/' && domain[domain_len-2] == '.') {
domain[domain_len-2] = '/';
domain[domain_len-1] = '\0';
}
dot = strchr(domain,'.');
if(dot != NULL) {
canon = apr_pstrdup(rule->ruleset->mp, domain);
ret = verify_gsb(gsb, msr, canon, strlen(canon));
if(ret > 0) {
set_match_to_tx(msr, capture, canon, 0);
if (! *error_msg) {
*error_msg = apr_psprintf(msr->mp, "Gsb lookup for \"%s\" succeeded.",
log_escape_nq(msr->mp, canon));
}
str = apr_pstrdup(rule->ruleset->mp,match);
base = apr_strtok(str,"/",&savedptr);
if(base != NULL)
set_match_to_tx(msr, capture, base, 1);
return 1;
}
base = apr_strtok(canon,"?",&savedptr);
if(base != NULL) {
ret = verify_gsb(gsb, msr, base, strlen(base));
if(ret > 0) {
set_match_to_tx(msr, capture, base, 0);
if (! *error_msg) {
*error_msg = apr_psprintf(msr->mp, "Gsb lookup for \"%s\" succeeded.",
log_escape_nq(msr->mp, base));
}
str = apr_pstrdup(rule->ruleset->mp,match);
base = apr_strtok(str,"/",&savedptr);
if(base != NULL)
set_match_to_tx(msr, capture, base, 1);
return 1;
}
}
url = apr_palloc(rule->ruleset->mp, strlen(canon));
count_slash = 0;
while(*canon != '\0') {
switch (*canon) {
case '/':
ptr = apr_psprintf(rule->ruleset->mp,"%s/",url);
ret = verify_gsb(gsb, msr, ptr, strlen(ptr));
if(ret > 0) {
set_match_to_tx(msr, capture, ptr, 0);
if (! *error_msg) {
*error_msg = apr_psprintf(msr->mp, "Gsb lookup for \"%s\" succeeded.",
log_escape_nq(msr->mp, ptr));
}
str = apr_pstrdup(rule->ruleset->mp,match);
base = apr_strtok(str,"/",&savedptr);
if(base != NULL)
set_match_to_tx(msr, capture, base, 1);
return 1;
}
break;
}
url[count_slash] = *canon;
count_slash++;
canon++;
}
}
}
/* Do the same for subdomains */
for(j=0; j<strlen(match); j++) {
if(match[j] == '/') {
s_pos = j;
break;
}
}
str = apr_pstrdup(rule->ruleset->mp, match);
while (*str != '\0') {
switch(*str) {
case '.':
domain++;
domain_len = strlen(domain);
d_pos = strchr(domain,'.') - domain;
if(s_pos >= 0 && d_pos >= 0 && d_pos > s_pos)
break;
if(*domain != '/') {
if(domain[domain_len-1] == '.')
domain[domain_len-1] = '\0';
if(domain[domain_len-1] == '/' && domain[domain_len-2] == '.') {
domain[domain_len-2] = '/';
domain[domain_len-1] = '\0';
}
dot = strchr(domain,'.');
if(dot != NULL) {
canon = apr_pstrdup(rule->ruleset->mp, domain);
ret = verify_gsb(gsb, msr, canon, strlen(canon));
if(ret > 0) {
set_match_to_tx(msr, capture, canon, 0);
if (! *error_msg) {
*error_msg = apr_psprintf(msr->mp, "Gsb lookup for \"%s\" succeeded.",
log_escape_nq(msr->mp, canon));
}
str = apr_pstrdup(rule->ruleset->mp,match);
base = apr_strtok(str,"/",&savedptr);
if(base != NULL)
set_match_to_tx(msr, capture, base, 1);
return 1;
}
base = apr_strtok(canon,"?",&savedptr);
if(base != NULL) {
ret = verify_gsb(gsb, msr, base, strlen(base));
if(ret > 0) {
set_match_to_tx(msr, capture, base, 0);
if (! *error_msg) {
*error_msg = apr_psprintf(msr->mp, "Gsb lookup for \"%s\" succeeded.",
log_escape_nq(msr->mp, base));
}
str = apr_pstrdup(rule->ruleset->mp,match);
base = apr_strtok(str,"/",&savedptr);
if(base != NULL)
set_match_to_tx(msr, capture, base, 1);
return 1;
}
}
url = apr_palloc(rule->ruleset->mp, strlen(canon));
count_slash = 0;
while(*canon != '\0') {
switch (*canon) {
case '/':
ptr = apr_psprintf(rule->ruleset->mp,"%s/",url);
ret = verify_gsb(gsb, msr, ptr, strlen(ptr));
if(ret > 0) {
set_match_to_tx(msr, capture, ptr, 0);
if (! *error_msg) {
*error_msg = apr_psprintf(msr->mp, "Gsb lookup for \"%s\" succeeded.",
log_escape_nq(msr->mp, ptr));
}
str = apr_pstrdup(rule->ruleset->mp,match);
base = apr_strtok(str,"/",&savedptr);
if(base != NULL)
set_match_to_tx(msr, capture, base, 1);
return 1;
}
break;
}
url[count_slash] = *canon;
count_slash++;
canon++;
}
}
}
break;
}
domain = str;
domain++;
str++;
}
}
}
offset = ovector[1];
}
return 0;
}
/* within */
static int msre_op_within_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 = 0;
unsigned int i, i_max;
str->value = (char *)rule->op_param;
if (str->value == NULL) {
*error_msg = "Internal Error: match string is null.";
return -1;
}
str->value_len = strlen(str->value);
if (error_msg == NULL) return -1;
*error_msg = NULL;
expand_macros(msr, str, rule, msr->mp);
match = (const char *)str->value;
match_length = str->value_len;
/* If the given target is null we give up without a match */
if (var->value == NULL) {
/* No match. */
return 0;
}
target = var->value;
target_length = var->value_len;
/* The empty string always matches */
if (target_length == 0) {
/* Match. */
*error_msg = apr_psprintf(msr->mp, "String match within \"\" at %s.",
var->name);
return 1;
}
/* This is impossible to match */
if (target_length > match_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 = match_length - target_length;
for (i = 0; i <= i_max; i++) {
if (match[i] == target[0]) {
if (memcmp((target + 1), (match + i + 1), (target_length - 1)) == 0) {
/* match. */
*error_msg = apr_psprintf(msr->mp, "String match within \"%s\" at %s.",
log_escape_ex(msr->mp, match, match_length),
var->name);
return 1;
}
}
}
/* No match. */
return 0;
}
/* 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 = 0;
unsigned int i, i_max;
str->value = (char *)rule->op_param;
if (str->value == NULL) {
*error_msg = "Internal Error: match string is null.";
return -1;
}
str->value_len = strlen(str->value);
if (error_msg == NULL) return -1;
*error_msg = NULL;
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;
}
/* The empty string always matches */
if (match_length == 0) {
/* Match. */
*error_msg = apr_psprintf(msr->mp, "String match \"\" at %s.", var->name);
return 1;
}
/* This is impossible to match */
if (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++) {
/* First character matched - avoid func call */
if (target[i] == match[0]) {
/* See if remaining matches */
if ( (match_length == 1)
|| (memcmp((match + 1), (target + i + 1), (match_length - 1)) == 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;
}
/** libinjection detectSQLi
* links against files in libinjection directory
* See www.client9.com/libinjection for details
*/
static int msre_op_detectSQLi_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
char **error_msg) {
char fingerprint[8];
int issqli;
int capture;
issqli = libinjection_sqli(var->value, var->value_len, fingerprint);
capture = apr_table_get(rule->actionset->actions, "capture") ? 1 : 0;
if (issqli) {
set_match_to_tx(msr, capture, fingerprint, 0);
*error_msg = apr_psprintf(msr->mp, "detected SQLi using libinjection with fingerprint '%s'",
fingerprint);
if (msr->txcfg->debuglog_level >= 9) {
msr_log(msr, 9, "ISSQL: libinjection fingerprint '%s' matched input '%s'",
fingerprint,
log_escape_ex(msr->mp, var->value, var->value_len));
}
} else {
if (msr->txcfg->debuglog_level >= 9) {
msr_log(msr, 9, "ISSQL: not sqli, no libinjection sqli fingerprint matched input '%s'",
log_escape_ex(msr->mp, var->value, var->value_len));
}
}
return issqli;
}
/** libinjection detectXSS
*/
static int msre_op_detectXSS_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
char **error_msg) {
int is_xss;
is_xss = libinjection_xss(var->value, var->value_len);
if (is_xss) {
*error_msg = apr_psprintf(msr->mp, "detected XSS using libinjection.");
if (msr->txcfg->debuglog_level >= 9) {
msr_log(msr, 9, "IS_XSS: libinjection detected XSS.");
}
} else {
if (msr->txcfg->debuglog_level >= 9) {
msr_log(msr, 9, "IS_XSS: not XSS, libinjection was not able to find any XSS.");
}
}
return is_xss;
}
/* containsWord */
static int msre_op_containsWord_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 = 0;
unsigned int i, i_max;
int rc = 0;
str->value = (char *)rule->op_param;
if (str->value == NULL) {
*error_msg = "Internal Error: match string is null.";
return -1;
}
str->value_len = strlen(str->value);
if (error_msg == NULL) return -1;
*error_msg = NULL;
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;
}
/* The empty string always matches */
if (match_length == 0) {
/* Match. */
*error_msg = apr_psprintf(msr->mp, "String match \"\" at %s.", var->name);
return 1;
}
/* This is impossible to match */
if (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++) {
/* Previous char must have been a start or non-word */
if ((i > 0) && (apr_isalnum(target[i-1])||(target[i-1] == '_')))
continue;
/* First character matched - avoid func call */
if (target[i] == match[0]) {
/* See if remaining matches */
if ( (match_length == 1)
|| (memcmp((match + 1), (target + i + 1), (match_length - 1)) == 0))
{
/* check boundaries */
if (i == i_max) {
/* exact/end word match */
rc = 1;
}
else if (!(apr_isalnum(target[i + match_length])||(target[i + match_length] == '_'))) {
/* start/mid word match */
rc = 1;
}
}
}
}
if (rc == 1) {
/* Maybe a 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. */
*error_msg = NULL;
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;
if (str->value == NULL) {
*error_msg = "Internal Error: match string is null.";
return -1;
}
str->value_len = strlen(str->value);
if (error_msg == NULL) return -1;
*error_msg = NULL;
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 (memcmp(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;
if (str->value == NULL) {
*error_msg = "Internal Error: match string is null.";
return -1;
}
str->value_len = strlen(str->value);
if (error_msg == NULL) return -1;
*error_msg = NULL;
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;
}
/* The empty string always matches */
if (match_length == 0) {
/* Match. */
*error_msg = apr_psprintf(msr->mp, "String match \"\" at %s.", var->name);
return 1;
}
/* This is impossible to match */
if (match_length > target_length) {
/* No match. */
return 0;
}
if (memcmp(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;
if (str->value == NULL) {
*error_msg = "Internal Error: match string is null.";
return -1;
}
str->value_len = strlen(str->value);
if (error_msg == NULL) return -1;
*error_msg = NULL;
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;
}
/* The empty string always matches */
if (match_length == 0) {
/* Match. */
*error_msg = apr_psprintf(msr->mp, "String match \"\" at %s.", var->name);
return 1;
}
/* This is impossible to match */
if (match_length > target_length) {
/* No match. */
return 0;
}
if (memcmp(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;
}
/* strmatch */
static int msre_op_strmatch_param_init(msre_rule *rule, char **error_msg) {
const apr_strmatch_pattern *compiled_pattern;
char *processed = NULL;
const char *pattern = rule->op_param;
unsigned short int op_len;
if (error_msg == NULL) return -1;
*error_msg = NULL;
op_len = strlen(pattern);
/* Process pattern */
processed = parse_pm_content(pattern, op_len, rule, error_msg);
if (processed == NULL) {
return 0;
}
/* Compile pattern */
compiled_pattern = apr_strmatch_precompile(rule->ruleset->mp, processed, 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_strmatch_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;
}
/* 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;
}
if (msr->xml->well_formed != 1) {
*error_msg = apr_psprintf(msr->mp,
"XML: DTD validation failed because content is not well formed.");
return 1;
}
/* Make sure there were no other generic processing errors */
if (msr->msc_reqbody_error) {
*error_msg = apr_psprintf(msr->mp,
"XML: DTD validation could not proceed due to previous"
" processing errors.");
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;
}
/* Send validator errors/warnings to msr_log */
/* NOTE: No xmlDtdSetValidErrors()? */
cvp->error = (xmlSchemaValidityErrorFunc)msr_log_error;
cvp->warning = (xmlSchemaValidityErrorFunc)msr_log_warn;
cvp->userData = msr;
if (!xmlValidateDtd(cvp, msr->xml->doc, dtd)) {
*error_msg = "XML: DTD validation failed.";
xmlFreeValidCtxt(cvp);
xmlFreeDtd(dtd);
return 1; /* No match. */
}
if (msr->txcfg->debuglog_level >= 4) {
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;
}
if (msr->xml->well_formed != 1) {
*error_msg = apr_psprintf(msr->mp,
"XML: Schema validation failed because content is not well formed.");
return 1;
}
/* Make sure there were no other generic processing errors */
if (msr->msc_reqbody_error) {
*error_msg = apr_psprintf(msr->mp,
"XML: Schema validation could not proceed due to previous"
" processing errors.");
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;
}
/* Send parser errors/warnings to msr_log */
xmlSchemaSetParserErrors(parserCtx, (xmlSchemaValidityErrorFunc)msr_log_error, (xmlSchemaValidityWarningFunc)msr_log_warn, msr);
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;
}
/* Send validator errors/warnings to msr_log */
xmlSchemaSetValidErrors(validCtx, (xmlSchemaValidityErrorFunc)msr_log_error, (xmlSchemaValidityWarningFunc)msr_log_warn, msr);
rc = xmlSchemaValidateDoc(validCtx, msr->xml->doc);
if (rc != 0) {
*error_msg = "XML: Schema validation failed.";
xmlSchemaFree(schema);
xmlSchemaFreeParserCtxt(parserCtx);
return 1; /* No match. */
}
if (msr->txcfg->debuglog_level >= 4) {
msr_log(msr, 4, "XML: Successfully validated payload against Schema: %s", rule->op_param);
}
xmlSchemaFree(schema);
xmlSchemaFreeValidCtxt(validCtx);
return 0;
}
/* verifyCC */
/**
* Luhn Mod-10 Method (ISO 2894/ANSI 4.13)
*/
static int luhn_verify(const char *ccnumber, int len) {
int sum[2] = { 0, 0 };
int odd = 0;
int digits = 0;
int i;
/* Weighted lookup table which is just a precalculated (i = index):
* i*2 + (( (i*2) > 9 ) ? -9 : 0)
*/
static const int wtable[10] = {0, 2, 4, 6, 8, 1, 3, 5, 7, 9}; /* weight lookup table */
/* Add up only digits (weighted digits via lookup table)
* for both odd and even CC numbers to avoid 2 passes.
*/
for (i = 0; i < len; i++) {
if (apr_isdigit(ccnumber[i])) {
sum[0] += (!odd ? wtable[ccnumber[i] - '0'] : (ccnumber[i] - '0'));
sum[1] += (odd ? wtable[ccnumber[i] - '0'] : (ccnumber[i] - '0'));
odd = 1 - odd; /* alternate weights */
digits++;
}
}
/* No digits extracted */
if (digits == 0) return 0;
/* Do a mod 10 on the sum */
sum[odd] %= 10;
/* If the result is a zero the card is valid. */
return sum[odd] ? 0 : 1;
}
static int msre_op_verifyCC_init(msre_rule *rule, char **error_msg) {
const char *errptr = NULL;
int erroffset;
msc_regex_t *regex;
if (error_msg == NULL) return -1;
*error_msg = NULL;
/* Compile rule->op_param */
regex = msc_pregcomp_ex(rule->ruleset->mp, rule->op_param, PCRE_DOTALL | PCRE_MULTILINE, &errptr, &erroffset, msc_pcre_match_limit, msc_pcre_match_limit_recursion);
if (regex == NULL) {
*error_msg = apr_psprintf(rule->ruleset->mp, "Error compiling pattern (offset %d): %s",
erroffset, errptr);
return 0;
}
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;
int offset;
int matched_bytes = 0;
char *qspos = NULL;
const char *parm = NULL;
msc_parm *mparm = NULL;
#ifdef WITH_PCRE_STUDY
#ifdef WITH_PCRE_JIT
int jit;
#endif
#endif
if (error_msg == NULL) return -1;
*error_msg = NULL;
if (regex == NULL) {
*error_msg = "Internal Error: regex data is null.";
return -1;
}
memset(ovector, 0, sizeof(ovector));
#ifdef WITH_PCRE_STUDY
#ifdef WITH_PCRE_JIT
if (msr->txcfg->debuglog_level >= 4) {
rc = msc_fullinfo(regex, PCRE_INFO_JIT, &jit);
if ((rc != 0) || (jit != 1)) {
*error_msg = apr_psprintf(rule->ruleset->mp,
"Rule %pp [id \"%s\"][file \"%s\"][line \"%d\"] - "
"Execution error - "
"Does not support JIT (%d)",
rule,((rule->actionset != NULL)&&((rule->actionset->id != NULL)&&
(rule->actionset->id != NOT_SET_P))) ? rule->actionset->id : "-",
rule->filename != NULL ? rule->filename : "-",
rule->line_num,rc);
msr_log(msr, 4, "%s.", *error_msg);
}
}
#endif
#endif
/* If the given target is null run against an empty
* string. This is a behaviour consistent with previous
* releases.
*/
if (var->value == NULL) {
target = "";
target_length = 0;
} else {
target = var->value;
target_length = var->value_len;
}
for (offset = 0; ((unsigned int)offset < target_length) && (is_cc == 0); offset++) {
if (msr->txcfg->debuglog_level >= 9) {
if (offset > 0) {
msr_log(msr, 9, "Continuing CC# search at target offset %d.", offset);
}
}
rc = msc_regexec_ex(regex, target, target_length, offset, PCRE_NOTEMPTY, ovector, 30, &my_error_msg);
/* If there was no match, then we are done. */
if (rc == PCRE_ERROR_NOMATCH) {
break;
}
if (rc < -1) {
*error_msg = apr_psprintf(msr->mp, "CC# regex execution failed: %s", my_error_msg);
return -1;
}
/* Verify a match. */
if (rc > 0) {
const char *match = target + ovector[0];
int length = ovector[1] - ovector[0];
int i = 0;
offset = ovector[2*i];
/* Check the Luhn using the match string */
is_cc = luhn_verify(match, length);
/* Not a CC number, then try another match where we left off. */
if (!is_cc) {
if (msr->txcfg->debuglog_level >= 9) {
msr_log(msr, 9, "CC# Luhn check failed at target offset %d: \"%.*s\"", offset, length, match);
}
continue;
}
/* We have a potential CC number and need to set any captures
* and we are done.
*/
matched_bytes = apr_table_get(rule->actionset->actions, "sanitizeMatchedBytes") ? 1 : 0;
if(!matched_bytes)
matched_bytes = apr_table_get(rule->actionset->actions, "sanitiseMatchedBytes") ? 1 : 0;
if (apr_table_get(rule->actionset->actions, "capture")) {
for(; i < rc; i++) {
msc_string *s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
if (s == NULL) return -1;
s->name = apr_psprintf(msr->mp, "%d", i);
if (s->name == NULL) return -1;
s->name_len = strlen(s->name);
s->value = apr_pstrmemdup(msr->mp, match, length);
if (s->value == NULL) return -1;
s->value_len = length;
apr_table_setn(msr->tx_vars, s->name, (void *)s);
if (msr->txcfg->debuglog_level >= 9) {
msr_log(msr, 9, "Added regex subexpression to TX.%d: %s", i,
log_escape_nq_ex(msr->mp, s->value, s->value_len));
}
if((matched_bytes == 1) && (var != NULL) && (var->name != NULL)) {
qspos = apr_psprintf(msr->mp, "%s", var->name);
parm = strstr(qspos, ":");
if (parm != NULL) {
parm++;
mparm = apr_palloc(msr->mp, sizeof(msc_parm));
if (mparm == NULL)
continue;
mparm->value = apr_pstrmemdup(msr->mp,s->value,s->value_len);
mparm->pad_1 = rule->actionset->arg_min;
mparm->pad_2 = rule->actionset->arg_max;
apr_table_addn(msr->pattern_to_sanitize, parm, (void *)mparm);
} else {
mparm = apr_palloc(msr->mp, sizeof(msc_parm));
if (mparm == NULL)
continue;
mparm->value = apr_pstrmemdup(msr->mp,s->value,s->value_len);
apr_table_addn(msr->pattern_to_sanitize, qspos, (void *)mparm);
}
}
}
}
/* Unset the remaining TX vars (from previous invocations). */
for(; i <= 9; i++) {
char buf[24];
apr_snprintf(buf, sizeof(buf), "%i", i);
apr_table_unset(msr->tx_vars, buf);
}
break;
}
}
if (is_cc) {
/* Match. */
/* This message will be logged. */
*error_msg = apr_psprintf(msr->mp, "CC# match \"%s\" at %s. [offset \"%d\"]",
regex->pattern, var->name, offset);
return 1;
}
/* No match. */
return 0;
}
/**
* \brief Check for a valid CPF
*
* \param cpfnumber Pointer to cpf
* \param len cpf length
*
* \retval 0 On Invalid CPF
* \retval 1 On Valid CPF
*/
static int cpf_verify(const char *cpfnumber, int len) {
int factor, part_1, part_2, var_len = len;
unsigned int sum = 0, i = 0, cpf_len = 11, c;
int cpf[11];
char s_cpf[11];
char bad_cpf[11][11] = { "00000000000",
"01234567890",
"11111111111",
"22222222222",
"33333333333",
"44444444444",
"55555555555",
"66666666666",
"77777777777",
"88888888888",
"99999999999"};
while((*cpfnumber != '\0') && ( var_len > 0)) {
if(*cpfnumber != '-' || *cpfnumber != '.') {
if(i < cpf_len && isdigit(*cpfnumber)) {
s_cpf[i] = *cpfnumber;
cpf[i] = convert_to_int(*cpfnumber);
i++;
}
}
cpfnumber++;
var_len--;
}
if (i != cpf_len)
return 0;
else {
for(i = 0; i< cpf_len; i++) {
if(strncmp(s_cpf,bad_cpf[i],cpf_len) == 0) {
return 0;
}
}
}
part_1 = convert_to_int(s_cpf[cpf_len-2]);
part_2 = convert_to_int(s_cpf[cpf_len-1]);
c = cpf_len;
for(i = 0; i < 9; i++) {
sum += (cpf[i] * --c);
}
factor = (sum % cpf_len);
if(factor < 2) {
cpf[9] = 0;
} else {
cpf[9] = cpf_len-factor;
}
sum = 0;
c = cpf_len;
for(i = 0;i < 10; i++)
sum += (cpf[i] * c--);
factor = (sum % cpf_len);
if(factor < 2) {
cpf[10] = 0;
} else {
cpf[10] = cpf_len-factor;
}
if(part_1 == cpf[9] && part_2 == cpf[10])
return 1;
return 0;
}
/**
* \brief Init function to CPF operator
*
* \param rule ModSecurity rule struct
* \param error_msg Error message
*
* \retval 0 On Failure
* \retval 1 On Success
*/
static int msre_op_verifyCPF_init(msre_rule *rule, char **error_msg) {
const char *errptr = NULL;
int erroffset;
msc_regex_t *regex;
if (error_msg == NULL) return -1;
*error_msg = NULL;
/* Compile rule->op_param */
regex = msc_pregcomp_ex(rule->ruleset->mp, rule->op_param, PCRE_DOTALL | PCRE_MULTILINE, &errptr, &erroffset, msc_pcre_match_limit, msc_pcre_match_limit_recursion);
if (regex == NULL) {
*error_msg = apr_psprintf(rule->ruleset->mp, "Error compiling pattern (offset %d): %s",
erroffset, errptr);
return 0;
}
rule->op_param_data = regex;
return 1; /* OK */
}
/**
* \brief Execution function to CPF operator
*
* \param msr ModSecurity transaction resource
* \param rule ModSecurity rule struct
* \param var ModSecurity variable struct
* \param error_msg Error message
*
* \retval -1 On Failure
* \retval 1 On Match
* \retval 0 On No Match
*/
static int msre_op_verifyCPF_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_cpf = 0;
int offset;
int matched_bytes = 0;
char *qspos = NULL;
const char *parm = NULL;
msc_parm *mparm = NULL;
#ifdef WITH_PCRE_STUDY
#ifdef WITH_PCRE_JIT
int jit;
#endif
#endif
if (error_msg == NULL) return -1;
*error_msg = NULL;
if (regex == NULL) {
*error_msg = "Internal Error: regex data is null.";
return -1;
}
memset(ovector, 0, sizeof(ovector));
#ifdef WITH_PCRE_STUDY
#ifdef WITH_PCRE_JIT
if (msr->txcfg->debuglog_level >= 4) {
rc = msc_fullinfo(regex, PCRE_INFO_JIT, &jit);
if ((rc != 0) || (jit != 1)) {
*error_msg = apr_psprintf(rule->ruleset->mp,
"Rule %pp [id \"%s\"][file \"%s\"][line \"%d\"] - "
"Execution error - "
"Does not support JIT (%d)",
rule,((rule->actionset != NULL)&&((rule->actionset->id != NULL)&&
(rule->actionset->id != NOT_SET_P))) ? rule->actionset->id : "-",
rule->filename != NULL ? rule->filename : "-",
rule->line_num,rc);
msr_log(msr, 4, "%s.", *error_msg);
}
}
#endif
#endif
/* If the given target is null run against an empty
* string. This is a behaviour consistent with previous
* releases.
*/
if (var->value == NULL) {
target = "";
target_length = 0;
} else {
target = var->value;
target_length = var->value_len;
}
for (offset = 0; ((unsigned int)offset < target_length) && (is_cpf == 0); offset++) {
if (msr->txcfg->debuglog_level >= 9) {
if (offset > 0) {
msr_log(msr, 9, "Continuing CPF# search at target offset %d.", offset);
}
}
rc = msc_regexec_ex(regex, target, target_length, offset, PCRE_NOTEMPTY, ovector, 30, &my_error_msg);
/* If there was no match, then we are done. */
if (rc == PCRE_ERROR_NOMATCH) {
break;
}
if (rc < -1) {
*error_msg = apr_psprintf(msr->mp, "CPF# regex execution failed: %s", my_error_msg);
return -1;
}
/* Verify a match. */
if (rc > 0) {
const char *match = target + ovector[0];
int length = ovector[1] - ovector[0];
int i = 0;
offset = ovector[2*i];
/* Check CPF using the match string */
is_cpf = cpf_verify(match, length);
/* Not a CPF number, then try another match where we left off. */
if (!is_cpf) {
if (msr->txcfg->debuglog_level >= 9) {
msr_log(msr, 9, "CPF# check failed at target offset %d: \"%.*s\"", offset, length, match);
}
continue;
}
/* We have a potential CPF number and need to set any captures
* and we are done.
*/
matched_bytes = apr_table_get(rule->actionset->actions, "sanitizeMatchedBytes") ? 1 : 0;
if(!matched_bytes)
matched_bytes = apr_table_get(rule->actionset->actions, "sanitiseMatchedBytes") ? 1 : 0;
if (apr_table_get(rule->actionset->actions, "capture")) {
for(; i < rc; i++) {
msc_string *s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
if (s == NULL) return -1;
s->name = apr_psprintf(msr->mp, "%d", i);
if (s->name == NULL) return -1;
s->name_len = strlen(s->name);
s->value = apr_pstrmemdup(msr->mp, match, length);
if (s->value == NULL) return -1;
s->value_len = length;
apr_table_setn(msr->tx_vars, s->name, (void *)s);
if (msr->txcfg->debuglog_level >= 9) {
msr_log(msr, 9, "Added regex subexpression to TX.%d: %s", i,
log_escape_nq_ex(msr->mp, s->value, s->value_len));
}
if((matched_bytes == 1) && (var != NULL) && (var->name != NULL)) {
qspos = apr_psprintf(msr->mp, "%s", var->name);
parm = strstr(qspos, ":");
if (parm != NULL) {
parm++;
mparm = apr_palloc(msr->mp, sizeof(msc_parm));
if (mparm == NULL)
continue;
mparm->value = apr_pstrmemdup(msr->mp,s->value,s->value_len);
mparm->pad_1 = rule->actionset->arg_min;
mparm->pad_2 = rule->actionset->arg_max;
apr_table_addn(msr->pattern_to_sanitize, parm, (void *)mparm);
} else {
mparm = apr_palloc(msr->mp, sizeof(msc_parm));
if (mparm == NULL)
continue;
mparm->value = apr_pstrmemdup(msr->mp,s->value,s->value_len);
apr_table_addn(msr->pattern_to_sanitize, qspos, (void *)mparm);
}
}
}
}
/* Unset the remaining TX vars (from previous invocations). */
for(; i <= 9; i++) {
char buf[24];
apr_snprintf(buf, sizeof(buf), "%i", i);
apr_table_unset(msr->tx_vars, buf);
}
break;
}
}
if (is_cpf) {
/* Match. */
/* This message will be logged. */
*error_msg = apr_psprintf(msr->mp, "CPF# match \"%s\" at %s. [offset \"%d\"]",
regex->pattern, var->name, offset);
return 1;
}
/* No match. */
return 0;
}
/**
* \brief Check for a valid SSN
*
* \param msr ModSecurity transaction source
* \param ssnumber Pointer to ssn
* \param len ssn length
*
* \retval 0 On Invalid SSN
* \retval 1 On Valid SSN
*/
static int ssn_verify(modsec_rec *msr, const char *ssnumber, int len) {
int i;
int num[9];
int digits = 0;
int area, serial, grp;
int sequencial = 0;
int repetitions = 0;
char *str_area;
char *str_grp;
char *str_serial;
for (i = 0; i < len; i++) {
if (apr_isdigit(ssnumber[i])) {
if (digits < 9)
num[digits] = convert_to_int(ssnumber[i]);
digits++;
}
}
/* Not a valid number */
if (digits != 9)
goto invalid;
for (i=0; i < 8; i++) {
if (num[i] == (num[i+1]-1))
sequencial++;
if (num[i] == num[i+1])
repetitions++;
}
/* We are blocking when all numbers were sequencial or repeated */
if (sequencial == 8)
goto invalid;
if (repetitions == 8)
goto invalid;
str_area = apr_psprintf(msr->mp,"%d%d%d",num[0],num[1],num[2]);
str_grp = apr_psprintf(msr->mp,"%d%d",num[3],num[4]);
str_serial = apr_psprintf(msr->mp,"%d%d%d%d",num[5],num[6],num[7],num[8]);
if(str_area == NULL || str_grp == NULL || str_serial == NULL)
goto invalid;
area = atoi(str_area);
grp = atoi(str_grp);
serial = atoi(str_serial);
/* Cannot has seroed fields */
if (area == 0 || serial == 0 || grp == 0)
goto invalid;
/* More tests */
if (area >= 740 || area == 666)
goto invalid;
return 1;
invalid:
return 0;
}
/**
* \brief Init function to SSN operator
*
* \param rule ModSecurity rule struct
* \param error_msg Error message
*
* \retval 0 On Failure
* \retval 1 On Success
*/
static int msre_op_verifySSN_init(msre_rule *rule, char **error_msg) {
const char *errptr = NULL;
int erroffset;
msc_regex_t *regex;
if (error_msg == NULL) return -1;
*error_msg = NULL;
/* Compile rule->op_param */
regex = msc_pregcomp_ex(rule->ruleset->mp, rule->op_param, PCRE_DOTALL | PCRE_MULTILINE, &errptr, &erroffset, msc_pcre_match_limit, msc_pcre_match_limit_recursion);
if (regex == NULL) {
*error_msg = apr_psprintf(rule->ruleset->mp, "Error compiling pattern (offset %d): %s",
erroffset, errptr);
return 0;
}
rule->op_param_data = regex;
return 1; /* OK */
}
/**
* \brief Execution function to SSN operator
*
* \param msr ModSecurity transaction resource
* \param rule ModSecurity rule struct
* \param var ModSecurity variable struct
* \param error_msg Error message
*
* \retval -1 On Failure
* \retval 1 On Match
* \retval 0 On No Match
*/
static int msre_op_verifySSN_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_ssn = 0;
int offset;
int matched_bytes = 0;
char *qspos = NULL;
const char *parm = NULL;
msc_parm *mparm = NULL;
#ifdef WITH_PCRE_STUDY
#ifdef WITH_PCRE_JIT
int jit;
#endif
#endif
if (error_msg == NULL) return -1;
*error_msg = NULL;
if (regex == NULL) {
*error_msg = "Internal Error: regex data is null.";
return -1;
}
memset(ovector, 0, sizeof(ovector));
#ifdef WITH_PCRE_STUDY
#ifdef WITH_PCRE_JIT
if (msr->txcfg->debuglog_level >= 4) {
rc = msc_fullinfo(regex, PCRE_INFO_JIT, &jit);
if ((rc != 0) || (jit != 1)) {
*error_msg = apr_psprintf(rule->ruleset->mp,
"Rule %pp [id \"%s\"][file \"%s\"][line \"%d\"] - "
"Execution error - "
"Does not support JIT (%d)",
rule,((rule->actionset != NULL)&&((rule->actionset->id != NULL)&&
(rule->actionset->id != NOT_SET_P))) ? rule->actionset->id : "-",
rule->filename != NULL ? rule->filename : "-",
rule->line_num,rc);
msr_log(msr, 4, "%s.", *error_msg);
}
}
#endif
#endif
/* If the given target is null run against an empty
* string. This is a behaviour consistent with previous
* releases.
*/
if (var->value == NULL) {
target = "";
target_length = 0;
} else {
target = var->value;
target_length = var->value_len;
}
for (offset = 0; ((unsigned int)offset < target_length) && (is_ssn == 0); offset++) {
if (msr->txcfg->debuglog_level >= 9) {
if (offset > 0) {
msr_log(msr, 9, "Continuing SSN# search at target offset %d.", offset);
}
}
rc = msc_regexec_ex(regex, target, target_length, offset, PCRE_NOTEMPTY, ovector, 30, &my_error_msg);
/* If there was no match, then we are done. */
if (rc == PCRE_ERROR_NOMATCH) {
break;
}
if (rc < -1) {
*error_msg = apr_psprintf(msr->mp, "SSN# regex execution failed: %s", my_error_msg);
return -1;
}
/* Verify a match. */
if (rc > 0) {
const char *match = target + ovector[0];
int length = ovector[1] - ovector[0];
int i = 0;
offset = ovector[2*i];
/* Check SSN using the match string */
is_ssn = ssn_verify(msr, match, length);
/* Not a SSN number, then try another match where we left off. */
if (!is_ssn) {
if (msr->txcfg->debuglog_level >= 9) {
msr_log(msr, 9, "SSN# check failed at target offset %d: \"%.*s\"", offset, length, match);
}
continue;
}
/* We have a potential SSN number and need to set any captures
* and we are done.
*/
matched_bytes = apr_table_get(rule->actionset->actions, "sanitizeMatchedBytes") ? 1 : 0;
if(!matched_bytes)
matched_bytes = apr_table_get(rule->actionset->actions, "sanitiseMatchedBytes") ? 1 : 0;
if (apr_table_get(rule->actionset->actions, "capture")) {
for(; i < rc; i++) {
msc_string *s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
if (s == NULL) return -1;
s->name = apr_psprintf(msr->mp, "%d", i);
if (s->name == NULL) return -1;
s->name_len = strlen(s->name);
s->value = apr_pstrmemdup(msr->mp, match, length);
if (s->value == NULL) return -1;
s->value_len = length;
apr_table_setn(msr->tx_vars, s->name, (void *)s);
if (msr->txcfg->debuglog_level >= 9) {
msr_log(msr, 9, "Added regex subexpression to TX.%d: %s", i,
log_escape_nq_ex(msr->mp, s->value, s->value_len));
}
if((matched_bytes == 1) && (var != NULL) && (var->name != NULL)) {
qspos = apr_psprintf(msr->mp, "%s", var->name);
parm = strstr(qspos, ":");
if (parm != NULL) {
parm++;
mparm = apr_palloc(msr->mp, sizeof(msc_parm));
if (mparm == NULL)
continue;
mparm->value = apr_pstrmemdup(msr->mp,s->value,s->value_len);
mparm->pad_1 = rule->actionset->arg_min;
mparm->pad_2 = rule->actionset->arg_max;
apr_table_addn(msr->pattern_to_sanitize, parm, (void *)mparm);
} else {
mparm = apr_palloc(msr->mp, sizeof(msc_parm));
if (mparm == NULL)
continue;
mparm->value = apr_pstrmemdup(msr->mp,s->value,s->value_len);
apr_table_addn(msr->pattern_to_sanitize, qspos, (void *)mparm);
}
}
}
}
/* Unset the remaining TX vars (from previous invocations). */
for(; i <= 9; i++) {
char buf[24];
apr_snprintf(buf, sizeof(buf), "%i", i);
apr_table_unset(msr->tx_vars, buf);
}
break;
}
}
if (is_ssn) {
/* Match. */
/* This message will be logged. */
*error_msg = apr_psprintf(msr->mp, "SSN# match \"%s\" at %s. [offset \"%d\"]",
regex->pattern, var->name, offset);
return 1;
}
/* No match. */
return 0;
}
/**
* 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;
*error_msg = NULL;
if (geo == NULL) {
msr_log(msr, 1, "Geo lookup for \"%s\" attempted without a database. Set SecGeoLookupDB.", log_escape(msr->mp, geo_host));
return 0;
}
rc = geo_lookup(msr, &rec, geo_host, error_msg);
if (rc <= 0) {
if (! *error_msg) {
*error_msg = apr_psprintf(msr->mp, "Geo lookup for \"%s\" failed at %s.", log_escape_nq(msr->mp, geo_host), var->name);
}
apr_table_clear(msr->geo_vars);
return rc;
}
if (! *error_msg) {
*error_msg = apr_psprintf(msr->mp, "Geo lookup for \"%s\" succeeded at %s.",
log_escape_nq(msr->mp, geo_host), var->name);
}
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->name_len = strlen(s->name);
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->name_len = strlen(s->name);
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, "COUNTRY_NAME");
s->name_len = strlen(s->name);
s->value = apr_pstrdup(msr->mp, rec.country_name ? rec.country_name : "");
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_CONTINENT");
s->name_len = strlen(s->name);
s->value = apr_pstrdup(msr->mp, rec.country_continent ? rec.country_continent : "");
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->name_len = strlen(s->name);
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->name_len = strlen(s->name);
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->name_len = strlen(s->name);
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->name_len = strlen(s->name);
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->name_len = strlen(s->name);
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->name_len = strlen(s->name);
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->name_len = strlen(s->name);
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;
unsigned int high8bits = 0;
char *name_to_check = NULL;
char *target = NULL;
apr_sockaddr_t *sa = NULL;
apr_status_t rc;
int capture = 0;
if (error_msg == NULL) return -1;
*error_msg = NULL;
capture = apr_table_get(rule->actionset->actions, "capture") ? 1 : 0;
/* 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 */
/* If we're using the httpBl blocklist, we need to add the key */
if(strstr(rule->op_param,"httpbl.org")) {
if (msr->txcfg->httpBlkey == NULL) {
if (msr->txcfg->debuglog_level >= 4) {
msr_log(msr, 4, "RBL httpBl called but no key defined: set SecHttpBlKey");
}
*error_msg = "RBL httpBl called but no key defined: set SecHttpBlKey";
} else {
name_to_check = apr_psprintf(msr->mp, "%s.%d.%d.%d.%d.%s", msr->txcfg->httpBlkey, h3, h2, h1, h0, rule->op_param);
}
} else {
/* regular IPv4 RBLs */
name_to_check = apr_psprintf(msr->mp, "%d.%d.%d.%d.%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) {
high8bits = sa->sa.sin.sin_addr.s_addr >> 24;
/* multi.uribl.com */
if(strstr(rule->op_param,"uribl.com")) {
switch(high8bits) {
case 2:
*error_msg = apr_psprintf(msr->r->pool, "RBL lookup of %s succeeded at %s (BLACK).",
log_escape_nq(msr->mp, name_to_check), var->name);
break;
case 4:
*error_msg = apr_psprintf(msr->r->pool, "RBL lookup of %s succeeded at %s (GREY).",
log_escape_nq(msr->mp, name_to_check), var->name);
break;
case 8:
*error_msg = apr_psprintf(msr->r->pool, "RBL lookup of %s succeeded at %s (RED).",
log_escape_nq(msr->mp, name_to_check), var->name);
break;
case 14:
*error_msg = apr_psprintf(msr->r->pool, "RBL lookup of %s succeeded at %s (BLACK,GREY,RED).",
log_escape_nq(msr->mp, name_to_check), var->name);
break;
case 255:
*error_msg = apr_psprintf(msr->r->pool, "RBL lookup of %s succeeded at %s (DNS IS BLOCKED).",
log_escape_nq(msr->mp, name_to_check), var->name);
break;
default:
*error_msg = apr_psprintf(msr->r->pool, "RBL lookup of %s succeeded at %s (WHITE).",
log_escape_nq(msr->mp, name_to_check), var->name);
break;
}
set_match_to_tx(msr, capture, *error_msg, 0);
} else
if(strstr(rule->op_param,"spamhaus.org")) {
switch(high8bits) {
case 2:
case 3:
*error_msg = apr_psprintf(msr->r->pool, "RBL lookup of %s succeeded at %s (Static UBE sources).",
log_escape_nq(msr->mp, name_to_check), var->name);
break;
case 4:
case 5:
case 6:
case 7:
*error_msg = apr_psprintf(msr->r->pool, "RBL lookup of %s succeeded at %s (Illegal 3rd party exploits).",
log_escape_nq(msr->mp, name_to_check), var->name);
break;
case 10:
case 11:
*error_msg = apr_psprintf(msr->r->pool, "RBL lookup of %s succeeded at %s (Delivering unauthenticated SMTP email).",
log_escape_nq(msr->mp, name_to_check), var->name);
break;
default:
*error_msg = apr_psprintf(msr->r->pool, "RBL lookup of %s succeeded at %s.",
log_escape_nq(msr->mp, name_to_check), var->name);
break;
}
set_match_to_tx(msr, capture, *error_msg, 0);
} else
if(strstr(rule->op_param,"httpbl.org")) {
char *respBl;
int first, days, score, type;
respBl = inet_ntoa(sa->sa.sin.sin_addr);
if (sscanf(respBl, "%d.%d.%d.%d", &first, &days, &score, &type) != 4) {
*error_msg = apr_psprintf(msr->r->pool, "RBL lookup of %s failed: bad response", log_escape_nq(msr->mp, name_to_check));
} else {
if (first != 127) {
*error_msg = apr_psprintf(msr->r->pool, "RBL lookup of %s failed: bad response", log_escape_nq(msr->mp, name_to_check));
}
else {
char *ptype;
switch(type) {
case 0:
ptype = "Search Engine";
break;
case 1:
ptype = "Suspicious IP";
break;
case 2:
ptype = "Harvester IP";
break;
case 3:
ptype = "Suspicious harvester IP";
break;
case 4:
ptype = "Comment spammer IP";
break;
case 5:
ptype = "Suspicious comment spammer IP";
break;
case 6:
ptype = "Harvester and comment spammer IP";
break;
case 7:
ptype = "Suspicious harvester comment spammer IP";
break;
default:
ptype = " ";
}
*error_msg = apr_psprintf(msr->r->pool, "RBL lookup of %s succeeded at %s. %s: %d days since last activity, threat score %d",
log_escape_nq(msr->mp, name_to_check), var->name,
ptype, days, score);
}
}
set_match_to_tx(msr, capture, *error_msg, 0);
/* end of httpBl code */
} else {
*error_msg = apr_psprintf(msr->r->pool, "RBL lookup of %s succeeded at %s.",
log_escape_nq(msr->mp, name_to_check), var->name);
set_match_to_tx(msr, capture, *error_msg, 0);
}
return 1; /* Match. */
}
if (msr->txcfg->debuglog_level >= 5) {
msr_log(msr, 5, "RBL lookup of %s failed at %s.", log_escape_nq(msr->mp, name_to_check), var->name);
}
/* No match. */
return 0;
}
/* fuzzyHash */
static int msre_op_fuzzy_hash_init(msre_rule *rule, char **error_msg)
{
#ifdef WITH_SSDEEP
struct fuzzy_hash_param_data *param_data;
struct fuzzy_hash_chunk *chunk, *t;
FILE *fp;
char *file;
int param_len,threshold;
char line[1024];
char *data = NULL;
char *threshold_str = NULL;
param_data = apr_palloc(rule->ruleset->mp,
sizeof(struct fuzzy_hash_param_data));
param_data->head = NULL;
data = apr_pstrdup(rule->ruleset->mp, rule->op_param);
threshold_str = data;
#endif
if (error_msg == NULL)
{
return -1;
}
*error_msg = NULL;
#ifdef WITH_SSDEEP
/* Sanity check */
param_len = strlen(threshold_str);
threshold_str = threshold_str + param_len;
if (param_len < 3)
{
goto invalid_parameters;
}
while (param_len - 1 > 0 && *threshold_str != ' ')
{
param_len--;
threshold_str--;
}
*threshold_str = '\0';
threshold_str++;
file = data;
threshold = atoi(threshold_str);
if ((file == NULL) || (is_empty_string(file)) || (threshold > 100) ||
(threshold < 1))
{
goto invalid_parameters;
}
file = resolve_relative_path(rule->ruleset->mp, rule->filename, file);
fp = fopen(file, "r");
if (!fp)
{
*error_msg = apr_psprintf(rule->ruleset->mp, "Not able to open file:" \
" %s.", file);
return -1;
}
while (read_line(line, sizeof(line), fp))
{
chunk = apr_palloc(rule->ruleset->mp,
sizeof(struct fuzzy_hash_chunk));
chunk->data = apr_pstrdup(rule->ruleset->mp, line);
chunk->next = NULL;
if (param_data->head == NULL) {
param_data->head = chunk;
} else {
t = param_data->head;
while (t->next) {
t = t->next;
}
t->next = chunk;
}
}
fclose(fp);
param_data->file = file;
param_data->threshold = threshold;
rule->op_param_data = param_data;
#else
rule->op_param_data = NULL;
return 1;
#endif
return 1;
invalid_parameters:
*error_msg = apr_psprintf(rule->ruleset->mp, "Operator @fuzzyHash " \
"requires valid parameters. File and threshold.");
return -1;
}
static int msre_op_fuzzy_hash_execute(modsec_rec *msr, msre_rule *rule,
msre_var *var, char **error_msg)
{
#ifdef WITH_SSDEEP
char result[FUZZY_MAX_RESULT];
struct fuzzy_hash_param_data *param = rule->op_param_data;
struct fuzzy_hash_chunk *chunk = param->head;
#endif
if (error_msg == NULL)
{
return -1;
}
*error_msg = NULL;
#ifdef WITH_SSDEEP
if (fuzzy_hash_buf(var->value, var->value_len, result))
{
*error_msg = apr_psprintf(rule->ruleset->mp, "Problems generating " \
"fuzzy hash.");
return -1;
}
while (chunk != NULL)
{
int i = fuzzy_compare(chunk->data, result);
msr_log(msr, 9, "%d (%s)", i, chunk->data);
if (i >= param->threshold)
{
*error_msg = apr_psprintf(msr->mp, "Fuzzy hash of %s matched " \
"with %s (from: %s). Score: %d.", var->name, chunk->data,
param->file, i);
return 1;
}
chunk = chunk->next;
}
#else
*error_msg = apr_psprintf(rule->ruleset->mp, "ModSecurity was not " \
"compiled with ssdeep support.");
return -1;
#endif
/* No match. */
return 0;
}
/* inspectFile */
static int msre_op_inspectFile_init(msre_rule *rule, char **error_msg) {
char *filename = (char *)rule->op_param;
if (error_msg == NULL) return -1;
*error_msg = NULL;
if ((filename == NULL)||(is_empty_string(filename))) {
*error_msg = apr_psprintf(rule->ruleset->mp, "Operator @inspectFile requires parameter.");
return -1;
}
filename = resolve_relative_path(rule->ruleset->mp, rule->filename, filename);
#if defined(WITH_LUA)
/* ENH Write & use string_ends(s, e). */
if (strlen(rule->op_param) > 4) {
char *p = filename + strlen(filename) - 4;
if ((p[0] == '.')&&(p[1] == 'l')&&(p[2] == 'u')&&(p[3] == 'a'))
{
msc_script *script = NULL;
/* Compile script. */
*error_msg = lua_compile(&script, filename, rule->ruleset->mp);
if (*error_msg != NULL) return -1;
rule->op_param_data = script;
}
}
#endif
if (rule->op_param_data == NULL) {
/* ENH Verify the script exists and that we have
* the rights to execute it.
*/
}
return 1;
}
static int msre_op_inspectFile_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
char **error_msg)
{
if (error_msg == NULL) return -1;
*error_msg = NULL;
if (rule->op_param_data == NULL) {
/* Execute externally, as native binary/shell script. */
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 (msr->txcfg->debuglog_level >= 4) {
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. */
}
}
#if defined(WITH_LUA)
else {
/* Execute internally, as Lua script. */
char *target = apr_pstrmemdup(msr->mp, var->value, var->value_len);
msc_script *script = (msc_script *)rule->op_param_data;
int rc;
rc = lua_execute(script, target, msr, rule, error_msg);
if (rc < 0) {
/* Error. */
return -1;
}
return rc;
}
#endif
/* 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: %d", 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: %d",
start);
return 0;
}
if ((end < 0)||(end > 255)) {
*error_msg = apr_psprintf(rule->ruleset->mp, "Invalid range end value: %d", end);
return 0;
}
if (start > end) {
*error_msg = apr_psprintf(rule->ruleset->mp, "Invalid range: %d-%d", 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 %d in %s outside range: %s", x, var->name, rule->op_param);
}
count++;
}
}
if (count == 0) return 0; /* Valid - no match. */
*error_msg = apr_psprintf(msr->mp, "Found %d byte(s) in %s outside range: %s.",
count, var->name, 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 :
/* Encoding is valid */
*error_msg = apr_psprintf(msr->mp, "Valid URL Encoding at %s.", var->name);
break;
case -2 :
*error_msg = apr_psprintf(msr->mp, "Invalid URL Encoding: Non-hexadecimal "
"digits used at %s.", var->name);
return 1; /* Invalid match. */
break;
case -3 :
*error_msg = apr_psprintf(msr->mp, "Invalid URL Encoding: Not enough characters "
"at the end of input at %s.", var->name);
return 1; /* Invalid match. */
break;
case -1 :
default :
*error_msg = apr_psprintf(msr->mp, "Invalid URL Encoding: Internal Error (rc = %d) at %s", rc, var->name);
return -1;
break;
}
/* No match. */
return 0;
}
/* validateUtf8Encoding */
/* NOTE: This is over-commented for ease of verification */
static int detect_utf8_character(const unsigned char *p_read, unsigned int length) {
int unicode_len = 0;
unsigned int d = 0;
unsigned char c;
if (p_read == NULL) return UNICODE_ERROR_DECODING_ERROR;
c = *p_read;
/* If first byte begins with binary 0 it is single byte encoding */
if ((c & 0x80) == 0) {
/* single byte unicode (7 bit ASCII equivilent) has no validation */
return 1;
}
/* If first byte begins with binary 110 it is two byte encoding*/
else if ((c & 0xE0) == 0xC0) {
/* check we have at least two bytes */
if (length < 2) unicode_len = UNICODE_ERROR_CHARACTERS_MISSING;
/* check second byte starts with binary 10 */
else if (((*(p_read + 1)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING;
else {
unicode_len = 2;
/* compute character number */
d = ((c & 0x1F) << 6) | (*(p_read + 1) & 0x3F);
}
}
/* If first byte begins with binary 1110 it is three byte encoding */
else if ((c & 0xF0) == 0xE0) {
/* check we have at least three bytes */
if (length < 3) unicode_len = UNICODE_ERROR_CHARACTERS_MISSING;
/* check second byte starts with binary 10 */
else if (((*(p_read + 1)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING;
/* check third byte starts with binary 10 */
else if (((*(p_read + 2)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING;
else {
unicode_len = 3;
/* compute character number */
d = ((c & 0x0F) << 12) | ((*(p_read + 1) & 0x3F) << 6) | (*(p_read + 2) & 0x3F);
}
}
/* If first byte begins with binary 11110 it is four byte encoding */
else if ((c & 0xF8) == 0xF0) {
/* restrict characters to UTF-8 range (U+0000 - U+10FFFF)*/
if (c >= 0xF5) {
return UNICODE_ERROR_RESTRICTED_CHARACTER;
}
/* check we have at least four bytes */
if (length < 4) unicode_len = UNICODE_ERROR_CHARACTERS_MISSING;
/* check second byte starts with binary 10 */
else if (((*(p_read + 1)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING;
/* check third byte starts with binary 10 */
else if (((*(p_read + 2)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING;
/* check forth byte starts with binary 10 */
else if (((*(p_read + 3)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING;
else {
unicode_len = 4;
/* compute character number */
d = ((c & 0x07) << 18) | ((*(p_read + 1) & 0x3F) << 12) | ((*(p_read + 2) & 0x3F) < 6) | (*(p_read + 3) & 0x3F);
}
}
/* any other first byte is invalid (RFC 3629) */
else {
return UNICODE_ERROR_INVALID_ENCODING;
}
/* invalid UTF-8 character number range (RFC 3629) */
if ((d >= 0xD800) && (d <= 0xDFFF)) {
return UNICODE_ERROR_RESTRICTED_CHARACTER;
}
/* check for overlong */
if ((unicode_len == 4) && (d < 0x010000)) {
/* four byte could be represented with less bytes */
return UNICODE_ERROR_OVERLONG_CHARACTER;
}
else if ((unicode_len == 3) && (d < 0x0800)) {
/* three byte could be represented with less bytes */
return UNICODE_ERROR_OVERLONG_CHARACTER;
}
else if ((unicode_len == 2) && (d < 0x80)) {
/* two byte could be represented with less bytes */
return 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;) {
int rc = detect_utf8_character((unsigned char *)&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 "
"at %s. [offset \"%d\"]", var->name, i);
return 1;
break;
case UNICODE_ERROR_INVALID_ENCODING :
*error_msg = apr_psprintf(msr->mp, "Invalid UTF-8 encoding: "
"invalid byte value in character "
"at %s. [offset \"%d\"]", var->name, i);
return 1;
break;
case UNICODE_ERROR_OVERLONG_CHARACTER :
*error_msg = apr_psprintf(msr->mp, "Invalid UTF-8 encoding: "
"overlong character detected "
"at %s. [offset \"%d\"]", var->name, i);
return 1;
break;
case UNICODE_ERROR_RESTRICTED_CHARACTER :
*error_msg = apr_psprintf(msr->mp, "Invalid UTF-8 encoding: "
"use of restricted character "
"at %s. [offset \"%d\"]", var->name, i);
return 1;
break;
case UNICODE_ERROR_DECODING_ERROR :
*error_msg = apr_psprintf(msr->mp, "Error validating UTF-8 decoding "
"at %s. [offset \"%d\"]", var->name, i);
return 1;
break;
}
if (rc <= 0) {
*error_msg = apr_psprintf(msr->mp, "Internal error during UTF-8 validation "
"at %s.", var->name);
return 1;
}
i += rc;
bytes_left -= rc;
}
return 0;
}
/* eq */
static int msre_op_eq_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
char **error_msg)
{
msc_string str;
int left, right;
char *target = NULL;
if (error_msg == NULL) return -1;
*error_msg = NULL;
if ((var->value == NULL)||(rule->op_param == NULL)) {
/* NULL values do not match anything. */
return 0;
}
str.value = (char *)rule->op_param;
str.value_len = strlen(str.value);
expand_macros(msr, &str, rule, msr->mp);
target = apr_pstrmemdup(msr->mp, var->value, var->value_len);
if (target == NULL) return -1;
left = atoi(target);
right = atoi(str.value);
if (left != right) {
/* No match. */
return 0;
}
else {
*error_msg = apr_psprintf(msr->mp, "Operator EQ matched %d at %s.", right, var->name);
/* Match. */
return 1;
}
}
/* gt */
static int msre_op_gt_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
char **error_msg)
{
msc_string str;
int left, right;
char *target = NULL;
if ((var->value == NULL)||(rule->op_param == NULL)) {
/* NULL values do not match anything. */
return 0;
}
if (error_msg == NULL) return -1;
*error_msg = NULL;
if ((var->value == NULL)||(rule->op_param == NULL)) {
/* NULL values do not match anything. */
return 0;
}
str.value = (char *)rule->op_param;
str.value_len = strlen(str.value);
expand_macros(msr, &str, rule, msr->mp);
target = apr_pstrmemdup(msr->mp, var->value, var->value_len);
if (target == NULL) return -1;
left = atoi(target);
right = atoi(str.value);
if (left <= right) {
/* No match. */
return 0;
}
else {
*error_msg = apr_psprintf(msr->mp, "Operator GT matched %d at %s.", right, var->name);
/* Match. */
return 1;
}
}
/* lt */
static int msre_op_lt_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
char **error_msg)
{
msc_string str;
int left, right;
char *target = NULL;
if ((var->value == NULL)||(rule->op_param == NULL)) {
/* NULL values do not match anything. */
return 0;
}
if (error_msg == NULL) return -1;
*error_msg = NULL;
if ((var->value == NULL)||(rule->op_param == NULL)) {
/* NULL values do not match anything. */
return 0;
}
str.value = (char *)rule->op_param;
str.value_len = strlen(str.value);
expand_macros(msr, &str, rule, msr->mp);
target = apr_pstrmemdup(msr->mp, var->value, var->value_len);
if (target == NULL) return -1;
left = atoi(target);
right = atoi(str.value);
if (left >= right) {
/* No match. */
return 0;
}
else {
*error_msg = apr_psprintf(msr->mp, "Operator LT matched %d at %s.", right, var->name);
/* Match. */
return 1;
}
}
/* ge */
static int msre_op_ge_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
char **error_msg)
{
msc_string str;
int left, right;
char *target = NULL;
if ((var->value == NULL)||(rule->op_param == NULL)) {
/* NULL values do not match anything. */
return 0;
}
if (error_msg == NULL) return -1;
*error_msg = NULL;
if ((var->value == NULL)||(rule->op_param == NULL)) {
/* NULL values do not match anything. */
return 0;
}
str.value = (char *)rule->op_param;
str.value_len = strlen(str.value);
expand_macros(msr, &str, rule, msr->mp);
target = apr_pstrmemdup(msr->mp, var->value, var->value_len);
if (target == NULL) return -1;
left = atoi(target);
right = atoi(str.value);
if (left < right) {
/* No match. */
return 0;
}
else {
*error_msg = apr_psprintf(msr->mp, "Operator GE matched %d at %s.", right, var->name);
/* Match. */
return 1;
}
}
/* le */
static int msre_op_le_execute(modsec_rec *msr, msre_rule *rule, msre_var *var,
char **error_msg)
{
msc_string str;
int left, right;
char *target = NULL;
if ((var->value == NULL)||(rule->op_param == NULL)) {
/* NULL values do not match anything. */
return 0;
}
if (error_msg == NULL) return -1;
*error_msg = NULL;
if ((var->value == NULL)||(rule->op_param == NULL)) {
/* NULL values do not match anything. */
return 0;
}
str.value = (char *)rule->op_param;
str.value_len = strlen(str.value);
expand_macros(msr, &str, rule, msr->mp);
target = apr_pstrmemdup(msr->mp, var->value, var->value_len);
if (target == NULL) return -1;
left = atoi(target);
right = atoi(str.value);
if (left > right) {
/* No match. */
return 0;
}
else {
*error_msg = apr_psprintf(msr->mp, "Operator LE matched %d at %s.", right, var->name);
/* Match. */
return 1;
}
}
/* -------------------------------------------------------------------------- */
/**
*
*/
void msre_engine_register_default_operators(msre_engine *engine) {
/* unconditionalMatch */
msre_engine_op_register(engine,
"unconditionalMatch",
NULL,
msre_op_unconditionalmatch_execute
);
/* noMatch */
msre_engine_op_register(engine,
"noMatch",
NULL,
msre_op_nomatch_execute
);
/* ipmatch */
msre_engine_op_register(engine,
"ipmatch",
msre_op_ipmatch_param_init,
msre_op_ipmatch_execute
);
/* ipmatchFromFile */
msre_engine_op_register(engine,
"ipmatchFromFile",
msre_op_ipmatchFromFile_param_init,
msre_op_ipmatchFromFile_execute
);
/* ipmatchf */
msre_engine_op_register(engine,
"ipmatchf",
msre_op_ipmatchFromFile_param_init,
msre_op_ipmatchFromFile_execute
);
/* rsub */
#if !defined(MSC_TEST)
msre_engine_op_register(engine,
"rsub",
msre_op_rsub_param_init,
msre_op_rsub_execute
);
#endif /* MSC_TEST */
/* rx */
msre_engine_op_register(engine,
"rx",
msre_op_rx_param_init,
msre_op_rx_execute
);
/* validateEncyption */
msre_engine_op_register(engine,
"validateHash",
msre_op_validateHash_param_init,
msre_op_validateHash_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
);
/* pmf */
msre_engine_op_register(engine,
"pmf",
msre_op_pmFromFile_param_init,
msre_op_pm_execute
);
/* within */
msre_engine_op_register(engine,
"within",
NULL, /* ENH init function to flag var substitution */
msre_op_within_execute
);
/* contains */
msre_engine_op_register(engine,
"contains",
NULL, /* ENH init function to flag var substitution */
msre_op_contains_execute
);
/* containsWord */
msre_engine_op_register(engine,
"containsWord",
NULL, /* ENH init function to flag var substitution */
msre_op_containsWord_execute
);
/* detectSQLi */
msre_engine_op_register(engine,
"detectSQLi",
NULL,
msre_op_detectSQLi_execute
);
/* detectXSS */
msre_engine_op_register(engine,
"detectXSS",
NULL,
msre_op_detectXSS_execute
);
/* streq */
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
);
/* strmatch */
msre_engine_op_register(engine,
"strmatch",
msre_op_strmatch_param_init,
msre_op_strmatch_execute
);
/* 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
);
/* verifyCC */
msre_engine_op_register(engine,
"verifyCC",
msre_op_verifyCC_init,
msre_op_verifyCC_execute
);
/* verifyCPF */
msre_engine_op_register(engine,
"verifyCPF",
msre_op_verifyCPF_init,
msre_op_verifyCPF_execute
);
/* verifySSN */
msre_engine_op_register(engine,
"verifySSN",
msre_op_verifySSN_init,
msre_op_verifySSN_execute
);
/* geoLookup */
msre_engine_op_register(engine,
"geoLookup",
NULL,
msre_op_geoLookup_execute
);
/* gsbLookup */
msre_engine_op_register(engine,
"gsbLookup",
msre_op_gsbLookup_param_init,
msre_op_gsbLookup_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",
msre_op_inspectFile_init,
msre_op_inspectFile_execute
);
/* fuzzy_hash */
msre_engine_op_register(engine,
"fuzzyHash",
msre_op_fuzzy_hash_init,
msre_op_fuzzy_hash_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
);
}