diff --git a/apache2/Makefile.am b/apache2/Makefile.am index 60f24579..e7bf787b 100644 --- a/apache2/Makefile.am +++ b/apache2/Makefile.am @@ -21,6 +21,7 @@ mod_security2_la_SOURCES = acmp.c \ msc_parsers.c \ msc_pcre.c \ msc_release.c \ + msc_remote_rules.c \ msc_reqbody.c \ msc_tree.c \ msc_unicode.c \ diff --git a/apache2/Makefile.win b/apache2/Makefile.win index eab972ae..5699673d 100644 --- a/apache2/Makefile.win +++ b/apache2/Makefile.win @@ -58,6 +58,7 @@ OBJS = mod_security2.obj apache2_config.obj apache2_io.obj apache2_util.obj \ msc_reqbody.obj msc_geo.obj msc_gsb.obj msc_crypt.obj msc_tree.obj msc_unicode.obj acmp.obj msc_lua.obj \ msc_release.obj \ msc_status_engine.obj \ + msc_remote_rules.obj \ msc_json.obj \ libinjection/libinjection_html5.obj \ libinjection/libinjection_sqli.obj \ diff --git a/apache2/apache2_config.c b/apache2/apache2_config.c index 30ba91fe..e1506ba8 100644 --- a/apache2/apache2_config.c +++ b/apache2/apache2_config.c @@ -2213,6 +2213,50 @@ static const char *cmd_rule_engine(cmd_parms *cmd, void *_dcfg, const char *p1) return NULL; } +static const char *cmd_remote_rules(cmd_parms *cmd, void *_dcfg, const char *p1, + const char *p2) +{ + char *error_msg = NULL; + directory_config *dcfg = (directory_config *)_dcfg; + if (dcfg == NULL) return NULL; + + // FIXME: make it https only. + // if (strncasecmp(p1, "https", 5) != 0) { + if (strncasecmp(p2, "http", 4) != 0) { + return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for " \ + " %s, expected an HTTPS address.", p2); + } + + // FIXME: Should we handle more then one server at once? + if (remote_rules_server != NULL) + { + return apr_psprintf(cmd->pool, "ModSecurity: " \ + "SecRemoteRules cannot be used more than once."); + } + + remote_rules_server = apr_pcalloc(cmd->pool, sizeof(msc_remote_rules_server)); + if (remote_rules_server == NULL) + { + return apr_psprintf(cmd->pool, "ModSecurity: " \ + "SecRemoteRules: Internal failure. Not enougth memory."); + } + + remote_rules_server->context = dcfg; + remote_rules_server->context_label = apr_pstrdup(cmd->pool, "Unkwon context"); + remote_rules_server->key = p1; + remote_rules_server->uri = p2; + remote_rules_server->amount_of_rules = 0; + + msc_remote_add_rules_from_uri(cmd, remote_rules_server, &error_msg); + if (error_msg != NULL) + { + return error_msg; + } + + return NULL; +} + + static const char *cmd_status_engine(cmd_parms *cmd, void *_dcfg, const char *p1) { if (strcasecmp(p1, "on") == 0) { @@ -3500,6 +3544,14 @@ const command_rec module_directives[] = { "On or Off" ), + AP_INIT_TAKE2 ( + "SecRemoteRules", + cmd_remote_rules, + NULL, + CMD_SCOPE_ANY, + "key and URI to the remote rules" + ), + AP_INIT_TAKE1 ( "SecXmlExternalEntity", cmd_xml_external_entity, diff --git a/apache2/mod_security2.c b/apache2/mod_security2.c index 7354f60e..3f601979 100644 --- a/apache2/mod_security2.c +++ b/apache2/mod_security2.c @@ -33,6 +33,8 @@ #include "apr_version.h" +#include "msc_remote_rules.h" + #if defined(WITH_LUA) #include "msc_lua.h" #endif @@ -66,6 +68,8 @@ unsigned long int DSOLOCAL msc_pcre_match_limit = 0; unsigned long int DSOLOCAL msc_pcre_match_limit_recursion = 0; +msc_remote_rules_server DSOLOCAL *remote_rules_server = NULL; + int DSOLOCAL status_engine_state = STATUS_ENGINE_DISABLED; int DSOLOCAL conn_limits_filter_state = MODSEC_DISABLED; @@ -752,6 +756,24 @@ static int hook_post_config(apr_pool_t *mp, apr_pool_t *mp_log, apr_pool_t *mp_t "SecStatusEngine to On."); } #endif + + if (remote_rules_server != NULL) + { + if (remote_rules_server->amount_of_rules == 1) + { + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, NULL, + "ModSecurity: Loaded %d rule from: '%s'.", + remote_rules_server->amount_of_rules, + remote_rules_server->uri); + } + else + { + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, NULL, + "ModSecurity: Loaded %d rule from: '%s'.", + remote_rules_server->amount_of_rules, + remote_rules_server->uri); + } + } } srand((unsigned int)(time(NULL) * getpid())); diff --git a/apache2/modsecurity.c b/apache2/modsecurity.c index 1260f38c..8233a14d 100644 --- a/apache2/modsecurity.c +++ b/apache2/modsecurity.c @@ -536,7 +536,7 @@ static apr_status_t modsecurity_process_phase_request_body(modsec_rec *msr) { apr_time_t time_before; apr_status_t rc = 0; - + if ((msr->allow_scope == ACTION_ALLOW_REQUEST)||(msr->allow_scope == ACTION_ALLOW)) { if (msr->txcfg->debuglog_level >= 4) { msr_log(msr, 4, "Skipping phase REQUEST_BODY (allow used)."); @@ -626,7 +626,7 @@ static apr_status_t modsecurity_process_phase_response_body(modsec_rec *msr) { */ static apr_status_t modsecurity_process_phase_logging(modsec_rec *msr) { apr_time_t time_before, time_after; - + if (msr->txcfg->debuglog_level >= 4) { msr_log(msr, 4, "Starting phase LOGGING."); } diff --git a/apache2/modsecurity.h b/apache2/modsecurity.h index 53fc3dda..aebe1189 100644 --- a/apache2/modsecurity.h +++ b/apache2/modsecurity.h @@ -33,6 +33,7 @@ typedef struct msc_arg msc_arg; typedef struct msc_string msc_string; typedef struct msc_parm msc_parm; +#include "msc_remote_rules.h" #include "msc_release.h" #include "msc_logging.h" #include "msc_multipart.h" @@ -144,6 +145,8 @@ extern DSOLOCAL unsigned long int msc_pcre_match_limit; extern DSOLOCAL unsigned long int msc_pcre_match_limit_recursion; +extern DSOLOCAL msc_remote_rules_server *remote_rules_server; + extern DSOLOCAL int status_engine_state; extern DSOLOCAL int conn_limits_filter_state; @@ -619,6 +622,14 @@ struct directory_config { /* xml */ int xml_external_entity; + + /* This will be used whenever ModSecurity will be ready + * to ask the server for newer rules. + */ +#if 0 + msc_remote_rules_server *remote_rules; + int remote_timeout; +#endif }; struct error_message_t { diff --git a/apache2/msc_remote_rules.c b/apache2/msc_remote_rules.c new file mode 100644 index 00000000..1bdb4258 --- /dev/null +++ b/apache2/msc_remote_rules.c @@ -0,0 +1,697 @@ +/* +* 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 "msc_remote_rules.h" +#include "msc_status_engine.h" + +#include +#include + +#include +#include +#include + +#ifndef AP_MAX_ARGC +#define AP_MAX_ARGC 64 +#endif + +#ifndef APU_HAVE_CRYPTO +#error Missing apu crypto module +#endif + +/** + * @brief Insert a new SecRule to be processed by ModSecurity + * + * This is a duplicate of `invoke_cmd', which is part of the standalone module. + * Apache versions does not include the standalone, thus, this is necessary for + * the Apache versions. Once it is here it may not be necessary to be part of + * the standalone module, but, for this version both function will co-exist + * avoiding problems with 3rd parties that are extending the standalone module. + * + * @note Prefer this function instead of `invoke_cmd` which is part of the + * standalone module. + * + * @param cmd pointer to command_rec structure. + * @param parms pointer to cmd_params strucutre. + * @param mconfig pointer to main config structure. + * @param args SecRule arguments. + * @retval NULL if everything worked as expected otherwise an error message. + * + */ +const char *msc_remote_invoke_cmd(const command_rec *cmd, cmd_parms *parms, + void *mconfig, const char *args) +{ + char *w, *w2, *w3; + const char *errmsg = NULL; + + if ((parms->override & cmd->req_override) == 0) + return apr_pstrcat(parms->pool, cmd->name, " not allowed here", NULL); + + parms->info = cmd->cmd_data; + parms->cmd = cmd; + + switch (cmd->args_how) { + case RAW_ARGS: +#ifdef RESOLVE_ENV_PER_TOKEN + args = ap_resolve_env(parms->pool,args); +#endif + return cmd->AP_RAW_ARGS(parms, mconfig, args); + + case TAKE_ARGV: + { + char *argv[AP_MAX_ARGC]; + int argc = 0; + + do { + w = ap_getword_conf(parms->pool, &args); + if (*w == '\0' && *args == '\0') { + break; + } + argv[argc] = w; + argc++; + } while (argc < AP_MAX_ARGC && *args != '\0'); + + return cmd->AP_TAKE_ARGV(parms, mconfig, argc, argv); + } + + case NO_ARGS: + if (*args != 0) + return apr_pstrcat(parms->pool, cmd->name, " takes no arguments", + NULL); + + return cmd->AP_NO_ARGS(parms, mconfig); + case TAKE1: + w = ap_getword_conf(parms->pool, &args); + + if (*w == '\0' || *args != 0) + return apr_pstrcat(parms->pool, cmd->name, " takes one argument", + cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL); + + return cmd->AP_TAKE1(parms, mconfig, w); + case TAKE2: + w = ap_getword_conf(parms->pool, &args); + w2 = ap_getword_conf(parms->pool, &args); + + if (*w == '\0' || *w2 == '\0' || *args != 0) + return apr_pstrcat(parms->pool, cmd->name, " takes two arguments", + cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL); + + return cmd->AP_TAKE2(parms, mconfig, w, w2); + case TAKE12: + w = ap_getword_conf(parms->pool, &args); + w2 = ap_getword_conf(parms->pool, &args); + + if (*w == '\0' || *args != 0) + return apr_pstrcat(parms->pool, cmd->name, " takes 1-2 arguments", + cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL); + + return cmd->AP_TAKE2(parms, mconfig, w, *w2 ? w2 : NULL); + case TAKE3: + w = ap_getword_conf(parms->pool, &args); + w2 = ap_getword_conf(parms->pool, &args); + w3 = ap_getword_conf(parms->pool, &args); + + if (*w == '\0' || *w2 == '\0' || *w3 == '\0' || *args != 0) + return apr_pstrcat(parms->pool, cmd->name, + " takes three arguments", + cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL); + + return cmd->AP_TAKE3(parms, mconfig, w, w2, w3); + case TAKE23: + + w = ap_getword_conf(parms->pool, &args); + w2 = ap_getword_conf(parms->pool, &args); + w3 = *args ? ap_getword_conf(parms->pool, &args) : NULL; + + if (*w == '\0' || *w2 == '\0' || *args != 0) + return apr_pstrcat(parms->pool, cmd->name, + " takes two or three arguments", + cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL); + + return cmd->AP_TAKE3(parms, mconfig, w, w2, w3); + case TAKE123: + w = ap_getword_conf(parms->pool, &args); + w2 = *args ? ap_getword_conf(parms->pool, &args) : NULL; + w3 = *args ? ap_getword_conf(parms->pool, &args) : NULL; + + if (*w == '\0' || *args != 0) + return apr_pstrcat(parms->pool, cmd->name, + " takes one, two or three arguments", + cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL); + + return cmd->AP_TAKE3(parms, mconfig, w, w2, w3); + case TAKE13: + w = ap_getword_conf(parms->pool, &args); + w2 = *args ? ap_getword_conf(parms->pool, &args) : NULL; + w3 = *args ? ap_getword_conf(parms->pool, &args) : NULL; + + if (*w == '\0' || (w2 && *w2 && !w3) || *args != 0) + return apr_pstrcat(parms->pool, cmd->name, + " takes one or three arguments", + cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL); + + return cmd->AP_TAKE3(parms, mconfig, w, w2, w3); + case ITERATE: + while (*(w = ap_getword_conf(parms->pool, &args)) != '\0') { + + errmsg = cmd->AP_TAKE1(parms, mconfig, w); + + if (errmsg && strcmp(errmsg, DECLINE_CMD) != 0) + return errmsg; + } + + return errmsg; + case ITERATE2: + w = ap_getword_conf(parms->pool, &args); + + if (*w == '\0' || *args == 0) + return apr_pstrcat(parms->pool, cmd->name, + " requires at least two arguments", + cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL); + + while (*(w2 = ap_getword_conf(parms->pool, &args)) != '\0') { + + errmsg = cmd->AP_TAKE2(parms, mconfig, w, w2); + + if (errmsg && strcmp(errmsg, DECLINE_CMD) != 0) + return errmsg; + } + + return errmsg; + case FLAG: + w = ap_getword_conf(parms->pool, &args); + + if (*w == '\0' || (strcasecmp(w, "on") && strcasecmp(w, "off"))) + return apr_pstrcat(parms->pool, cmd->name, " must be On or Off", + NULL); + + return cmd->AP_FLAG(parms, mconfig, strcasecmp(w, "off") != 0); + default: + return apr_pstrcat(parms->pool, cmd->name, + " is improperly configured internally (server bug)", + NULL); + } +} +/** + * @brief Fetch an URL and fill the content into a memory buffer. + * + * Fill an msc_curl_memory_buffer_t structure with the content of an given + * URL. + * + * @note While fetching the content, it will present the ModSecurity instance + * to the remote server, trough: ModSecurity Unique ID, ModSecurity + * status line and also, if given, key that can be used to + * authentication. Such data is presented in the following HTTP headers: + * - ModSec-status + * - ModSec-unique-id + * - ModSec-key + * + * @warning Cleanup the memory after use it. + * + * @param mp pointer to the memory pool. + * @param uri URI to be fetched. + * @param key KEY to be present as ModSec-key. + * @param chunk pointer to an msc_curl_memory_buffer_t struct. + * @param error_msg pointer an char pointer, filled is something went wrong. + * + * @retval n>=0 everything went fine. + * @retval n<-1 Something wrong happened, further details on error_msg. + * + */ +int msc_remote_grab_content(apr_pool_t *mp, const char *uri, const char *key, + struct msc_curl_memory_buffer_t *chunk, char **error_msg) +{ + CURL *curl; + CURLcode res; + + char id[(APR_SHA1_DIGESTSIZE*2) + 1]; + char *apr_id = NULL; + char *beacon_str = NULL; + char *beacon_apr = NULL; + char *header_key = NULL; + int beacon_str_len = 0; + + memset(id, '\0', sizeof(id)); + if (msc_status_engine_unique_id(id)) + { + sprintf(id, "no unique id"); + } + + apr_id = apr_psprintf(mp, "ModSec-unique-id: %s", id); + + curl_global_init(CURL_GLOBAL_ALL); + curl = curl_easy_init(); + + beacon_str_len = msc_beacon_string(NULL, 0); + + beacon_str = malloc(sizeof(char) * beacon_str_len + 1); + if (beacon_str == NULL) + { + beacon_str = "Failed to retrieve beacon string"; + beacon_apr = apr_psprintf(mp, "ModSec-status: %s", beacon_str); + } + else + { + msc_beacon_string(beacon_str, beacon_str_len); + beacon_apr = apr_psprintf(mp, "ModSec-status: %s", beacon_str); + free(beacon_str); + } + + if (key != NULL) + { + header_key = apr_psprintf(mp, "ModSec-key: %s", key); + } + + if (curl) + { + struct curl_slist *headers_chunk = NULL; + curl_easy_setopt(curl, CURLOPT_URL, remote_rules_server->uri); + + headers_chunk = curl_slist_append(headers_chunk, apr_id); + headers_chunk = curl_slist_append(headers_chunk, beacon_apr); + if (key != NULL) + { + headers_chunk = curl_slist_append(headers_chunk, header_key); + } + + /* send all data to this function */ + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, msc_curl_write_memory_cb); + + /* we pass our 'chunk' struct to the callback function */ + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)chunk); + + /* some servers don't like requests that are made without a user-agent + field, so we provide one */ + curl_easy_setopt(curl, CURLOPT_USERAGENT, "modesecurity"); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers_chunk); + + res = curl_easy_perform(curl); + + if (res != CURLE_OK) + { + *error_msg = apr_psprintf(mp, "Failed to fetch \"%s\" error: %s ", + remote_rules_server->uri, curl_easy_strerror(res)); + return -1; + } + + curl_slist_free_all(headers_chunk); + } + + curl_easy_cleanup(curl); + + curl_global_cleanup(); + return 0; +} + +/** + * @brief Setup an apr_crypto_key_t from a given password and salt. + * + * apr_crypto_* demands the key to be in a format of an apr_crypto_key_t which + * is an structure that may be defined depending on the crypto provider, thus, + * making necessary for us to create this structure using apr internal + * functions. + * + * @warning We trust that the paramenter used in apr, such as the algorithm, + * key size and other parameters won't change, if they do it may + * break the interoperability with this function with others + * implementations, as the key will end up with a different value + * than the one expected. + * + * @param pool pointer to the memory pool to be used. + * @param key password to be used while creating the key. + * @param apr_key pointer to the pointer of an apr_crypto_key_t structure. + * @param f pointer to apr_crypto_t. + * @param salt string to be used as salt of the key generation + * @param error_msg pointer to char pointer, which is filled if something + * went wrong. + * + * @retval n>=0 everything went fine. + * @retval n<-1 Something wrong happened, check error_msg for further details. + * + */ +int msc_remote_enc_key_setup(apr_pool_t *pool, + const char *key, + apr_crypto_key_t **apr_key, + apr_crypto_t *f, + unsigned char *salt, + char **error_msg) +{ + apr_size_t key_len = strlen(key); + apr_size_t salt_len = 16; //FIXME: salt_len should not be hard coded. + + const int do_pad = 1; + apr_status_t rv; + + rv = apr_crypto_passphrase( + (apr_crypto_key_t **) apr_key, + NULL, + (const char *) key, + (apr_size_t) key_len, + (const unsigned char *) salt, + (apr_size_t) salt_len, + APR_KEY_AES_256, + APR_MODE_CBC, + (const int) do_pad, + (const int) 4096, + (const apr_crypto_t *) f, + (apr_pool_t *) pool); + + if (rv == APR_ENOKEY) + { + *error_msg = "Internal error - apr_crypto_passphrase: Missing key"; + return -1; + } + else if (rv == APR_EPADDING) + { + *error_msg = "Internal error - apr_crypto_passphrase: APR_EPADDING"; + return -1; + } + else if (rv == APR_EKEYTYPE) + { + *error_msg = "Internal error - apr_crypto_passphrase: APR_EKEYTYPE"; + return -1; + } + else if (rv == APR_EKEYTYPE) + { + *error_msg = "Internal error - apr_crypto_passphrase: APR_EKEYTYPE"; + return -1; + } + else if (rv != APR_SUCCESS) + { + *error_msg = "Internal error - apr_crypto_passphrase: Unknown error"; + return -1; + } + + return 0; +} + +/** + * @brief Decrypt an buffer into a memory buffer. + * + * Decrypt an msc_curl_memory_buffer_t structure into another + * msc_curl_memory_buffer_t. + * + * Using the key provided, it creates and apr_key and uses it to decript the + * content provided on chunk. The plain text content is saved under + * chunk_plain. + * + * @warning Cleanup memory after usage. + * + * @param pool pointer to the memory pool to be used. + * @param key pointer to the char array to be used as the key. + * @param chunk msc_curl_memory_buffer_t that contains the encrypted content. + * @param plain_text unsigned char which will hold the content of an url + * @param plain_text_len size of the plain_text buffer + * @param error_msg pointer to char pointer that is filled if something went + * wrong. + * + * @retval n>=0 everything went fine. + * @retval n<-1 Something wrong happened, further details on error_msg. + * + */ +int msc_remote_decrypt(apr_pool_t *pool, + const char *key, + struct msc_curl_memory_buffer_t *chunk, + unsigned char **plain_text, + apr_size_t *plain_text_len, + char **error_msg) +{ + apr_crypto_key_t *apr_key = NULL; + apr_crypto_t *f = NULL; + const apr_crypto_driver_t *driver = NULL; + const apu_err_t *err = NULL; + apr_status_t rv; + unsigned char *iv = NULL; + unsigned char *ciphered_text = NULL; + unsigned char *salt = NULL; + + apr_crypto_block_t *block = NULL; + apr_size_t block_size = 0; + apr_size_t len = 0; + + // FIXME: size should not be hardcoded. + // at least size of IV + Salt + if (chunk->size < 16+16+1) + { + *error_msg = "Unexpected content."; + return -1; + } + iv = chunk->memory; + salt = chunk->memory + 16; + ciphered_text = chunk->memory + (16 + 16); + + rv = apr_crypto_init(pool); + if (rv != APR_SUCCESS) + { + *error_msg = "Internal error: failed to init crypto"; + return -1; + } + +#ifndef APU_CRYPTO_RECOMMENDED_DRIVER + rv = apr_crypto_get_driver(&driver, "openssl", NULL, &err, pool); +#else + rv = apr_crypto_get_driver(&driver, APU_CRYPTO_RECOMMENDED_DRIVER, NULL, + &err, pool); +#endif + if (rv != APR_SUCCESS || driver == NULL) + { + *error_msg = "Internal error - apr_crypto_get_driver: Unknown error"; + return -1; + } + + rv = apr_crypto_make(&f, driver, NULL, pool); + if (rv != APR_SUCCESS) + { + *error_msg = "Internal error - apr_crypto_make: Unknown error"; + return -1; + } + + msc_remote_enc_key_setup(pool, key, &apr_key, f, salt, error_msg); + if (*error_msg != NULL) + { + return -1; + } + + rv = apr_crypto_block_decrypt_init(&block, &block_size, iv, apr_key, pool); + if (rv == APR_ENOKEY) + { + *error_msg = "Internal error - apr_crypto_block_decrypt_init: " \ + "Missing key"; + return -1; + } + else if (rv == APR_ENOIV) + { + *error_msg = "Internal error - apr_crypto_block_decrypt_init: " \ + "Missing IV"; + return -1; + } + else if (rv == APR_EKEYTYPE) + { + *error_msg = "Internal error - apr_crypto_block_decrypt_init: " \ + "Wrong key type"; + return -1; + } + else if (rv == APR_EKEYLENGTH) + { + *error_msg = "Internal error - apr_crypto_block_decrypt_init: " \ + "Wrong key length"; + return -1; + } + else if (rv != APR_SUCCESS) + { + *error_msg = "Internal error - apr_crypto_block_decrypt_init: " \ + "Unknown error"; + return -1; + } + + //FIXME: size should not be hardcoded like that. + // 32 = iv + salt size. + rv = apr_crypto_block_decrypt(plain_text, plain_text_len, + ciphered_text, (apr_size_t) chunk->size - (16 + 16) , + block); + + if (rv != APR_SUCCESS) + { + *error_msg = "Internal error - apr_crypto_block_decrypt: Failed to " \ + "decrypt"; + return -1; + } + + /* finalise the decryption */ + + rv = apr_crypto_block_decrypt_finish(*plain_text + *plain_text_len, &len, + block); + if (rv != APR_SUCCESS) + { + *error_msg = "Internal error - apr_crypto_block_decrypt_finish: " \ + "Failed to decrypt"; + return -1; + } + + apr_crypto_block_cleanup(block); + + return 0; +} + + +/** + * @brief Add SecRules from a given URI. + * + * Fetch the URI and using the key provided into params decrypt and install + * the downloaded set of rules. + * + * @warning Cleanup the memory may be necessary. + * + * @param orig_parms origin parms used at SecRemoteRule + * @param remote_rules_server pointer to the filled msc_remote_rules_server + * structure. + * @param error_msg pointer to char pointer that will be filled if something + * went wrong. + * + * + * @retval n>=0 everything went fine. + * @retval n<-1 Something wrong happened, further details on error_msg. + * + */ +int msc_remote_add_rules_from_uri(cmd_parms *orig_parms, + msc_remote_rules_server *remote_rules_server, + char **error_msg) +{ + struct msc_curl_memory_buffer_t chunk_encrypted; + unsigned char *plain_text = NULL; + int len = 0; + int start = 0; + int end = 0; + int added_rules = 0; + apr_size_t plain_text_len = 0; + + apr_pool_t *mp = orig_parms->pool; + + chunk_encrypted.size = 0; + chunk_encrypted.memory = NULL; + + msc_remote_grab_content(mp, remote_rules_server->uri, + remote_rules_server->key, &chunk_encrypted, error_msg); + if (*error_msg != NULL) + { + return -1; + } + + msc_remote_decrypt(mp, remote_rules_server->key, &chunk_encrypted, + &plain_text, + &plain_text_len, + error_msg); + if (*error_msg != NULL) + { + return -1; + } + + msc_remote_clean_chunk(&chunk_encrypted); + + len = 0; + plain_text_len = strlen(plain_text); + while (len < plain_text_len) + { + if (plain_text[len] == '\n') + { + const char *rule = NULL; + int tmp = len; + char *cmd_name; + char *word; + const command_rec *cmd; + ap_directive_t *newdir; + cmd_parms *parms = apr_pcalloc(mp, sizeof (cmd_parms)); + + rule = plain_text + start; + end = len; + plain_text[len] = '\0'; + + memcpy(parms, orig_parms, sizeof(cmd_parms)); + + if (*rule == '#' || *rule == '\0') + { + goto next; + } + + cmd_name = ap_getword_conf(mp, &rule); + cmd = ap_find_command(cmd_name, security2_module.cmds); + + if (cmd == NULL) + { + *error_msg = apr_pstrcat(mp, "Unknown command in config: ", + cmd_name, NULL); + return -1; + } + + newdir = apr_pcalloc(mp, sizeof(ap_directive_t)); + newdir->filename = "remote server"; + newdir->line_num = -1; + newdir->directive = cmd_name; + newdir->args = apr_pstrdup(mp, rule); + parms->directive = newdir; + +#ifdef WIN32 + // some config commands fail in APR when there are file + // permission issues or other OS-specific problems + // + __try + { +#endif + *error_msg = (char *) msc_remote_invoke_cmd(cmd, parms, + remote_rules_server->context, rule); + if (*error_msg != NULL) + { + return -1; + } + + added_rules++; +#ifdef WIN32 + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + error_msg = "Command failed to execute (check file/folder" \ + "permissions, syntax, etc.)."; + return -1; + } +#endif + +next: + start = end + 1; + } + len++; + } + + remote_rules_server->amount_of_rules = added_rules; +} + + +int msc_remote_clean_chunk(struct msc_curl_memory_buffer_t *chunk) +{ + if (chunk->size <= 0) + { + goto end; + } + + if (chunk->memory == NULL) + { + goto end; + } + + free(chunk->memory); + chunk->size = 0; + +end: + return 0; +} + diff --git a/apache2/msc_remote_rules.h b/apache2/msc_remote_rules.h new file mode 100644 index 00000000..20de8080 --- /dev/null +++ b/apache2/msc_remote_rules.h @@ -0,0 +1,67 @@ +/* +* 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. +*/ + +#ifndef MSC_REMOTE_RULES_H +#define MSC_REMOTE_RULES_H + +#include +#include +#include +#include + +#include +#include +#include "http_core.h" + +typedef struct msc_remote_rules_server msc_remote_rules_server; +struct msc_curl_memory_buffer_t; + +#include "modsecurity.h" + +struct msc_remote_rules_server { + directory_config *context; + const char *context_label; + const char *uri; + const char *key; + int amount_of_rules; +}; + +const char *msc_remote_invoke_cmd(const command_rec *cmd, cmd_parms *parms, + void *mconfig, const char *args); + +int msc_remote_grab_content(apr_pool_t *mp, const char *uri, const char *key, + struct msc_curl_memory_buffer_t *chunk, char **error_msg); + +int msc_remote_enc_key_setup(apr_pool_t *pool, + const char *key, + apr_crypto_key_t **apr_key, + apr_crypto_t *f, + unsigned char *salt, + char **error_msg); + +int msc_remote_decrypt(apr_pool_t *pool, + const char *key, + struct msc_curl_memory_buffer_t *chunk, + unsigned char **plain_text, + apr_size_t *plain_text_len, + char **error_msg); + +int msc_remote_add_rules_from_uri(cmd_parms *orig_parms, + msc_remote_rules_server *remote_rules_server, + char **error_msg); + +int msc_remote_clean_chunk(struct msc_curl_memory_buffer_t *chunk); + +#endif + diff --git a/apache2/msc_util.c b/apache2/msc_util.c index 1647bc44..2bdf9424 100644 --- a/apache2/msc_util.c +++ b/apache2/msc_util.c @@ -2634,12 +2634,12 @@ int ip_tree_from_uri(TreeRoot **rtree, char *uri, int beacon_str_len = 0; char *beacon_apr = NULL; struct msc_curl_memory_buffer_t chunk; - chunk.memory = malloc(1); /* will be grown as needed by the realloc above */ - chunk.size = 0; /* no data at this point */ char *word = NULL; char *brkt = NULL; char *sep = "\n"; + chunk.memory = malloc(1); /* will be grown as needed by the realloc above */ + chunk.size = 0; /* no data at this point */ if (create_radix_tree(mp, rtree, error_msg)) @@ -2834,7 +2834,17 @@ size_t msc_curl_write_memory_cb(void *contents, size_t size, size_t realsize = size * nmemb; struct msc_curl_memory_buffer_t *mem = (struct msc_curl_memory_buffer_t *)userp; - mem->memory = realloc(mem->memory, mem->size + realsize + 1); + if (mem->size == 0) + { + mem->memory = malloc(realsize + 1); + memset(mem->memory, '\0', sizeof(realsize + 1)); + } + else + { + mem->memory = realloc(mem->memory, mem->size + realsize + 1); + memset(mem->memory + mem->size, '\0', sizeof(realsize + 1)); + } + if(mem->memory == NULL) { /* out of memory! */ return 0; @@ -2847,3 +2857,31 @@ size_t msc_curl_write_memory_cb(void *contents, size_t size, return realsize; } +#ifdef WIN32 +char* strtok_r( + char *str, + const char *delim, + char **nextp) +{ + char *ret; + + if (str == NULL) + { + str = *nextp; + } + str += strspn(str, delim); + if (*str == '\0') + { + return NULL; + } + ret = str; + str += strcspn(str, delim); + if (*str) + { + *str++ = '\0'; + } + *nextp = str; + return ret; +} + +#endif diff --git a/apache2/msc_util.h b/apache2/msc_util.h index de2c3a87..a97b03ca 100644 --- a/apache2/msc_util.h +++ b/apache2/msc_util.h @@ -170,4 +170,8 @@ struct msc_curl_memory_buffer_t size_t size; }; +#ifdef WIN32 +char *strtok_r(char *str, const char *delim, char **nextp); +#endif + #endif diff --git a/iis/Makefile.win b/iis/Makefile.win index 9acc9085..fd6b6ec2 100644 --- a/iis/Makefile.win +++ b/iis/Makefile.win @@ -73,8 +73,9 @@ OBJS1 = mod_security2.obj apache2_config.obj apache2_io.obj apache2_util.obj \ msc_reqbody.obj msc_geo.obj msc_gsb.obj msc_unicode.obj acmp.obj msc_lua.obj \ msc_release.obj msc_crypt.obj msc_tree.obj \ msc_status_engine.obj \ - msc_json.obj - + msc_json.obj \ + msc_remote_rules.obj + OBJS2 = api.obj buckets.obj config.obj filters.obj hooks.obj regex.obj server.obj OBJS3 = main.obj moduleconfig.obj mymodule.obj OBJS4 = libinjection_html5.obj \ diff --git a/standalone/Makefile.am b/standalone/Makefile.am index 1e3875d5..8db4522c 100644 --- a/standalone/Makefile.am +++ b/standalone/Makefile.am @@ -22,6 +22,7 @@ standalone_la_SOURCES = ../apache2/acmp.c \ ../apache2/msc_parsers.c \ ../apache2/msc_pcre.c \ ../apache2/msc_release.c \ + ../apache2/msc_remote_rules.c \ ../apache2/msc_reqbody.c \ ../apache2/msc_tree.c \ ../apache2/msc_unicode.c \