diff --git a/apache2/Makefile.am b/apache2/Makefile.am index f79f50772d..897b7020df 100644 --- a/apache2/Makefile.am +++ b/apache2/Makefile.am @@ -8,7 +8,7 @@ pkglib_LTLIBRARIES = mod_security2.la mod_security2_la_SOURCES = mod_security2.c \ apache2_config.c apache2_io.c apache2_util.c \ re.c re_operators.c re_actions.c re_tfns.c \ - re_variables.c msc_logging.c msc_xml.c \ + re_variables.c msc_logging.c msc_xml.c msc_json.c \ msc_multipart.c modsecurity.c msc_parsers.c \ msc_util.c msc_pcre.c persist_dbm.c msc_reqbody.c \ msc_geo.c msc_gsb.c msc_crypt.c msc_tree.c msc_unicode.c acmp.c msc_lua.c msc_release.c diff --git a/apache2/modsecurity.c b/apache2/modsecurity.c index 0e6df481cb..0bdc3c4b03 100644 --- a/apache2/modsecurity.c +++ b/apache2/modsecurity.c @@ -19,6 +19,7 @@ #include "modsecurity.h" #include "msc_parsers.h" #include "msc_util.h" +#include "msc_json.h" #include "msc_xml.h" #include "apr_version.h" @@ -255,6 +256,9 @@ static apr_status_t modsecurity_tx_cleanup(void *data) { /* XML processor cleanup. */ if (msr->xml != NULL) xml_cleanup(msr); + /* JSON processor cleanup. */ + if (msr->json != NULL) json_cleanup(msr); + // TODO: Why do we ignore return code here? modsecurity_request_body_clear(msr, &my_error_msg); if (my_error_msg != NULL) { diff --git a/apache2/modsecurity.h b/apache2/modsecurity.h index 8eb8af88fc..afc7ed6528 100644 --- a/apache2/modsecurity.h +++ b/apache2/modsecurity.h @@ -38,6 +38,7 @@ typedef struct msc_parm msc_parm; #include "msc_multipart.h" #include "msc_pcre.h" #include "msc_util.h" +#include "msc_json.h" #include "msc_xml.h" #include "msc_geo.h" #include "msc_gsb.h" @@ -358,6 +359,7 @@ struct modsec_rec { multipart_data *mpd; /* MULTIPART processor data structure */ xml_data *xml; /* XML processor data structure */ + json_data *json; /* JSON processor data structure */ /* audit logging */ char *new_auditlog_boundary; diff --git a/apache2/modules.mk b/apache2/modules.mk index 3a8dd00adf..40327810ce 100644 --- a/apache2/modules.mk +++ b/apache2/modules.mk @@ -1,10 +1,10 @@ MOD_SECURITY2 = mod_security2 apache2_config apache2_io apache2_util \ - re re_operators re_actions re_tfns re_variables \ + re re_operators re_actions re_tfns re_variables msc_json \ msc_logging msc_xml msc_multipart modsecurity msc_parsers msc_util msc_pcre \ persist_dbm msc_reqbody pdf_protect msc_geo msc_gsb msc_crypt msc_tree msc_unicode acmp msc_lua H = re.h modsecurity.h msc_logging.h msc_multipart.h msc_parsers.h \ - msc_pcre.h msc_util.h msc_xml.h persist_dbm.h apache2.h pdf_protect.h \ + msc_pcre.h msc_util.h msc_xml.h persist_dbm.h apache2.h pdf_protect.h msc_json.h \ msc_geo.h msc_gsb.h msc_crypt.h msc_tree.h msc_unicode.h acmp.h utf8tables.h msc_lua.h ${MOD_SECURITY2:=.slo}: ${H} diff --git a/apache2/msc_json.c b/apache2/msc_json.c new file mode 100755 index 0000000000..8c89bfa393 --- /dev/null +++ b/apache2/msc_json.c @@ -0,0 +1,313 @@ +/* + * ModSecurity for Apache 2.x, http://www.modsecurity.org/ + * Copyright (c) 2004-2011 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_json.h" + +int json_add_argument(modsec_rec *msr, const char *value, unsigned length) +{ + msc_arg *arg; + + /** + * If we do not have a prefix, we cannot create a variable name + * to reference this argument; for now we simply ignore these + */ + if (!msr->json->current_key) { + msr_log(msr, 3, "Cannot add scalar value without an associated key"); + return 1; + } + + arg = (msc_arg *) apr_pcalloc(msr->mp, sizeof(msc_arg)); + + /** + * Argument name is 'prefix + current_key' + */ + if (msr->json->prefix) { + arg->name = apr_psprintf(msr->mp, "%s.%s", msr->json->prefix, + msr->json->current_key); + } + else { + arg->name = apr_psprintf(msr->mp, "%s", msr->json->current_key); + } + arg->name_len = strlen(arg->name); + + /** + * Argument value is copied from the provided string + */ + arg->value = apr_pstrmemdup(msr->mp, value, length); + arg->value_len = length; + + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Adding JSON argument '%s' with value '%s'", + arg->name, arg->value); + } + + apr_table_addn(msr->arguments, + log_escape_nq_ex(msr->mp, arg->name, arg->name_len), (void *) arg); + + return 1; +} + +/** + * yajl callback functions + * For more information on the function signatures and order, check + * http://lloyd.github.com/yajl/yajl-1.0.12/structyajl__callbacks.html + */ + +/** + * Callback for hash key values; we use those to define the variable names + * under ARGS. Whenever we reach a new key, we update the current key value. + */ +static int yajl_map_key(void *ctx, const unsigned char *key, unsigned int length) +{ + modsec_rec *msr = (modsec_rec *) ctx; + unsigned char *safe_key; + + /** + * yajl does not provide use with null-terminated strings, but + * rather expects us to copy the data from the key up to the + * length informed; we create a standalone null-termined copy + * in safe_key + */ + safe_key = apr_pstrndup(msr->mp, key, length); + + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "New JSON hash key '%s'", safe_key); + } + + /** + * TODO: How do we free the previously string value stored here? + */ + msr->json->current_key = safe_key; + + return 1; +} + +/** + * Callback for null values + * + * TODO: Is there a way to define true null parameter values instead of + * empty values? + */ +static int yajl_null(void *ctx) +{ + modsec_rec *msr = (modsec_rec *) ctx; + + return json_add_argument(msr, "", 0); +} + +/** + * Callback for boolean values + */ +static int yajl_boolean(void *ctx, int value) +{ + modsec_rec *msr = (modsec_rec *) ctx; + + if (value) { + return json_add_argument(msr, "true", strlen("true")); + } + else { + return json_add_argument(msr, "false", strlen("false")); + } +} + +/** + * Callback for string values + */ +static int yajl_string(void *ctx, const unsigned char *value, unsigned int length) +{ + modsec_rec *msr = (modsec_rec *) ctx; + + return json_add_argument(msr, value, length); +} + +/** + * Callback for numbers; YAJL can use separate callbacks for integers/longs and + * float/double values, but since we are not interested in using the numeric + * values here, we use a generic handler which uses numeric strings + */ +static int yajl_number(void *ctx, const unsigned char *value, unsigned int length) +{ + modsec_rec *msr = (modsec_rec *) ctx; + + return json_add_argument(msr, value, length); +} + +/** + * Callback for a new hash, which indicates a new subtree, labeled as the current + * argument name, is being created + */ +static int yajl_start_map(void *ctx) +{ + modsec_rec *msr = (modsec_rec *) ctx; + + /** + * If we do not have a current_key, this is a top-level hash, so we do not + * need to do anything + */ + if (!msr->json->current_key) return 1; + + /** + * Check if we are already inside a hash context, and append or create the + * current key name accordingly + */ + if (msr->json->prefix) { + msr->json->prefix = apr_psprintf(msr->mp, "%s.%s", msr->json->prefix, + msr->json->current_key); + } + else { + msr->json->prefix = apr_pstrdup(msr->mp, msr->json->current_key); + } + + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "New JSON hash context (prefix '%s')", msr->json->prefix); + } + + return 1; +} + +/** + * Callback for end hash, meaning the current subtree is being closed, and that + * we should go back to the parent variable label + */ +static int yajl_end_map(void *ctx) +{ + modsec_rec *msr = (modsec_rec *) ctx; + unsigned char *separator; + + /** + * If we have no prefix, then this is the end of a top-level hash and + * we don't do anything + */ + if (msr->json->prefix == NULL) return 1; + + /** + * Current prefix might or not include a separator character; top-level + * hash keys do not have separators in the variable name + */ + separator = strrchr(msr->json->prefix, '.'); + + if (separator) { + msr->json->prefix = apr_pstrmemdup(msr->mp, msr->json->prefix, + separator - msr->json->prefix); + msr->json->current_key = apr_psprintf(msr->mp, "%s", separator + 1); + } + else { + /** + * TODO: Check if it is safe to do this kind of pointer tricks + */ + msr->json->current_key = msr->json->prefix; + msr->json->prefix = (unsigned char *) NULL; + } + + return 1; +} + +/** + * Initialise JSON parser. + */ +int json_init(modsec_rec *msr, char **error_msg) { + /** + * yajl configuration and callbacks + */ + static yajl_parser_config config = { 0, 1 }; + static yajl_callbacks callbacks = { + yajl_null, + yajl_boolean, + NULL /* yajl_integer */, + NULL /* yajl_double */, + yajl_number, + yajl_string, + yajl_start_map, + yajl_map_key, + yajl_end_map, + NULL /* yajl_start_array */, + NULL /* yajl_end_array */ + }; + + if (error_msg == NULL) return -1; + *error_msg = NULL; + + msr_log(msr, 4, "JSON parser initialization"); + msr->json = apr_pcalloc(msr->mp, sizeof(json_data)); + if (msr->json == NULL) return -1; + + /** + * Prefix and current key are initially empty + */ + msr->json->prefix = (unsigned char *) NULL; + msr->json->current_key = (unsigned char *) NULL; + + /** + * yajl initialization + * + * yajl_parser_config definition: + * http://lloyd.github.com/yajl/yajl-1.0.12/structyajl__parser__config.html + * + * TODO: make UTF8 validation optional, as it depends on Content-Encoding + */ + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "yajl JSON parsing callback initialization"); + } + msr->json->handle = yajl_alloc(&callbacks, &config, NULL, msr); + + return 1; +} + +/** + * Feed one chunk of data to the JSON parser. + */ +int json_process_chunk(modsec_rec *msr, const char *buf, unsigned int size, char **error_msg) { + if (error_msg == NULL) return -1; + *error_msg = NULL; + + /* Feed our parser and catch any errors */ + msr->json->status = yajl_parse(msr->json->handle, buf, size); + if (msr->json->status != yajl_status_ok && + msr->json->status != yajl_status_insufficient_data) { + /* We need to free the yajl error message later, how to do this? */ + *error_msg = yajl_get_error(msr->json->handle, 0, buf, size); + } + + return 1; +} + +/** + * Finalise JSON parsing. + */ +int json_complete(modsec_rec *msr, char **error_msg) { + char *json_data = (char *) NULL; + + if (error_msg == NULL) return -1; + *error_msg = NULL; + + /* Wrap up the parsing process */ + msr->json->status = yajl_parse_complete(msr->json->handle); + if (msr->json->status != yajl_status_ok && + msr->json->status != yajl_status_insufficient_data) { + /* We need to free the yajl error message later, how to do this? */ + *error_msg = yajl_get_error(msr->json->handle, 0, NULL, 0); + } + + return 1; +} + +/** + * Frees the resources used for XML parsing. + */ +apr_status_t json_cleanup(modsec_rec *msr) { + msr_log(msr, 4, "JSON: Cleaning up JSON results"); + + return 1; +} + diff --git a/apache2/msc_json.h b/apache2/msc_json.h new file mode 100644 index 0000000000..06fa6c82fe --- /dev/null +++ b/apache2/msc_json.h @@ -0,0 +1,48 @@ +/* +* ModSecurity for Apache 2.x, http://www.modsecurity.org/ +* Copyright (c) 2004-2011 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_JSON_H_ +#define _MSC_JSON_H_ + +typedef struct json_data json_data; + +#include "modsecurity.h" +#include + +/* Structures */ +struct json_data { + /* yajl configuration and parser state */ + yajl_handle handle; + yajl_status status; + + /* error reporting and JSON array flag */ + unsigned char *yajl_error; + + /* prefix is used to create data hierarchy (i.e., 'parent.child.value') */ + unsigned char *prefix; + unsigned char *current_key; +}; + +/* Functions */ + +int DSOLOCAL json_init(modsec_rec *msr, char **error_msg); + +int DSOLOCAL json_process(modsec_rec *msr, const char *buf, + unsigned int size, char **error_msg); + +int DSOLOCAL json_complete(modsec_rec *msr, char **error_msg); + +apr_status_t DSOLOCAL json_cleanup(modsec_rec *msr); + +#endif diff --git a/apache2/msc_reqbody.c b/apache2/msc_reqbody.c index b76056bad0..2a5c949f12 100644 --- a/apache2/msc_reqbody.c +++ b/apache2/msc_reqbody.c @@ -127,6 +127,14 @@ apr_status_t modsecurity_request_body_start(modsec_rec *msr, char **error_msg) { msr_log(msr, 2, "%s", *error_msg); } } + else if (strcmp(msr->msc_reqbody_processor, "JSON") == 0) { + if (json_init(msr, &my_error_msg) < 0) { + *error_msg = apr_psprintf(msr->mp, "JSON parsing error (init): %s", my_error_msg); + msr->msc_reqbody_error = 1; + msr->msc_reqbody_error_msg = my_error_msg; + msr_log(msr, 2, "%s", *error_msg); + } + } else if (strcmp(msr->msc_reqbody_processor, "URLENCODED") == 0) { /* Do nothing, URLENCODED processor does not support streaming yet. */ } @@ -343,6 +351,18 @@ apr_status_t modsecurity_request_body_store(modsec_rec *msr, msr_log(msr, 2, "%s", *error_msg); } } + else if (strcmp(msr->msc_reqbody_processor, "JSON") == 0) { + /* Increase per-request data length counter. */ + msr->msc_reqbody_no_files_length += length; + + /* Process data as XML. */ + if (json_process_chunk(msr, data, length, &my_error_msg) < 0) { + *error_msg = apr_psprintf(msr->mp, "JSON parsing error: %s", my_error_msg); + msr->msc_reqbody_error = 1; + msr->msc_reqbody_error_msg = *error_msg; + msr_log(msr, 2, "%s", *error_msg); + } + } else if (strcmp(msr->msc_reqbody_processor, "URLENCODED") == 0) { /* Increase per-request data length counter. */ msr->msc_reqbody_no_files_length += length; @@ -600,6 +620,15 @@ apr_status_t modsecurity_request_body_end(modsec_rec *msr, char **error_msg) { return -1; } } + else if (strcmp(msr->msc_reqbody_processor, "JSON") == 0) { + if (json_complete(msr, &my_error_msg) < 0) { + *error_msg = apr_psprintf(msr->mp, "JSON parser error: %s", my_error_msg); + msr->msc_reqbody_error = 1; + msr->msc_reqbody_error_msg = *error_msg; + msr_log(msr, 2, "%s", *error_msg); + return -1; + } + } else if (strcmp(msr->msc_reqbody_processor, "URLENCODED") == 0) { return modsecurity_request_body_end_urlencoded(msr, error_msg); } diff --git a/configure.ac b/configure.ac index 05283b9326..fd018d0534 100644 --- a/configure.ac +++ b/configure.ac @@ -680,6 +680,9 @@ if test "$build_mlogc" -ne 0; then CHECK_CURL() fi +# Check for YAJL libs (for JSON body processor) +AC_SEARCH_LIBS([yajl_alloc], [yajl]) + AC_CONFIG_FILES([Makefile]) AC_CONFIG_FILES([tools/Makefile]) if test "$build_alp2" -ne 0; then diff --git a/standalone/Makefile.in b/standalone/Makefile.in index aa6e8acf7c..81e359380b 100644 --- a/standalone/Makefile.in +++ b/standalone/Makefile.in @@ -230,6 +230,7 @@ PCRE_CONFIG = @PCRE_CONFIG@ PCRE_CPPFLAGS = @PCRE_CPPFLAGS@ PCRE_LDADD = @PCRE_LDADD@ PCRE_LDFLAGS = @PCRE_LDFLAGS@ +PCRE_LD_PATH = @PCRE_LD_PATH@ PCRE_VERSION = @PCRE_VERSION@ PERL = @PERL@ PKG_CONFIG = @PKG_CONFIG@