/* * ModSecurity for Apache 2.x, http://www.modsecurity.org/ * Copyright (c) 2004-2022 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" #ifdef WITH_YAJL const char *base_offset=NULL; int json_add_argument(modsec_rec *msr, const char *value, unsigned length) { assert(msr != NULL); assert(msr->json != NULL); msc_arg *arg = (msc_arg *) NULL; /** * 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->json->current_key = ""; } 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; arg->value_origin_len = length; arg->value_origin_offset = value-base_offset; arg->origin = "JSON"; if (msr->txcfg->debuglog_level >= 9) { msr_log(msr, 9, "Adding JSON argument '%s' with value '%s'", arg->name, arg->value); } if (apr_table_elts(msr->arguments)->nelts >= msr->txcfg->arguments_limit) { if (msr->txcfg->debuglog_level >= 4) { msr_log(msr, 4, "Skipping request argument, over limit (%s): name \"%s\", value \"%s\"", arg->origin, log_escape_ex(msr->mp, arg->name, arg->name_len), log_escape_ex(msr->mp, arg->value, arg->value_len)); } msr->msc_reqbody_error = 1; msr->json->yajl_error = apr_psprintf(msr->mp, "More than %ld JSON keys", msr->txcfg->arguments_limit); return 0; } arg->marked_for_sanitization = 0; 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, size_t length) { modsec_rec *msr = (modsec_rec *) ctx; assert(msr != NULL); assert(msr->json != NULL); unsigned char *safe_key = (unsigned char *) NULL; /** * yajl does not provide us 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; assert(msr != NULL); return json_add_argument(msr, "", 0); } /** * Callback for boolean values */ static int yajl_boolean(void *ctx, int value) { modsec_rec *msr = (modsec_rec *) ctx; assert(msr != NULL); 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, size_t length) { modsec_rec *msr = (modsec_rec *) ctx; assert(msr != NULL); 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 char *value, size_t length) { modsec_rec *msr = (modsec_rec *) ctx; assert(msr != NULL); return json_add_argument(msr, value, length); } static int yajl_start_array(void *ctx) { modsec_rec *msr = (modsec_rec *) ctx; assert(msr != NULL); assert(msr->json != NULL); if (!msr->json->current_key && !msr->json->prefix) { msr->json->prefix = apr_pstrdup(msr->mp, "array"); msr->json->current_key = apr_pstrdup(msr->mp, "array"); } else 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); } msr->json->current_depth++; if (msr->json->current_depth > msr->txcfg->reqbody_json_depth_limit) { msr->json->depth_limit_exceeded = 1; return 0; } if (msr->txcfg->debuglog_level >= 9) { msr_log(msr, 9, "New JSON hash context (prefix '%s')", msr->json->prefix); } return 1; } static int yajl_end_array(void *ctx) { modsec_rec *msr = (modsec_rec *) ctx; assert(msr != NULL); assert(msr->json != NULL); unsigned char *separator = (unsigned char *) NULL; /** * 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); } else { /** * TODO: Check if it is safe to do this kind of pointer tricks */ msr->json->prefix = (unsigned char *) NULL; } msr->json->current_depth--; return 1; } /** * 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; assert(msr != NULL); assert(msr->json != NULL); /** * 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); } msr->json->current_depth++; if (msr->json->current_depth > msr->txcfg->reqbody_json_depth_limit) { msr->json->depth_limit_exceeded = 1; return 0; } 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; assert(msr != NULL); assert(msr->json != NULL); unsigned char *separator = (unsigned char *) NULL; /** * 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; } msr->json->current_depth--; return 1; } /** * Initialise JSON parser. */ int json_init(modsec_rec *msr, char **error_msg) { assert(msr != NULL); assert(error_msg != NULL); /** * yajl configuration and callbacks */ 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, yajl_start_array, yajl_end_array }; *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; msr->json->current_depth = 0; msr->json->depth_limit_exceeded = 0; /** * yajl initialization * * yajl_parser_config definition: * http://lloyd.github.io/yajl/yajl-2.0.1/yajl__parse_8h.html#aec816c5518264d2ac41c05469a0f986c * * 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, NULL, msr); yajl_config(msr->json->handle, yajl_allow_partial_values, 0); 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) { assert(msr != NULL); assert(msr->json != NULL); assert(error_msg != NULL); *error_msg = NULL; base_offset=buf; /* Feed our parser and catch any errors */ msr->json->status = yajl_parse(msr->json->handle, buf, size); if (msr->json->status != yajl_status_ok) { if (msr->json->depth_limit_exceeded) { *error_msg = "JSON depth limit exceeded"; } else { if (msr->json->yajl_error) *error_msg = msr->json->yajl_error; else { char* yajl_err = yajl_get_error(msr->json->handle, 0, buf, size); *error_msg = apr_pstrdup(msr->mp, yajl_err); yajl_free_error(msr->json->handle, yajl_err); } } return -1; } return 1; } /** * Finalise JSON parsing. */ int json_complete(modsec_rec *msr, char **error_msg) { assert(msr != NULL); assert(msr->json != NULL); assert(error_msg != NULL); *error_msg = NULL; /* Wrap up the parsing process */ msr->json->status = yajl_complete_parse(msr->json->handle); if (msr->json->status != yajl_status_ok) { if (msr->json->depth_limit_exceeded) { *error_msg = "JSON depth limit exceeded"; } else { char *yajl_err = yajl_get_error(msr->json->handle, 0, NULL, 0); *error_msg = apr_pstrdup(msr->mp, yajl_err); yajl_free_error(msr->json->handle, yajl_err); } return -1; } return 1; } /** * Frees the resources used for JSON parsing. */ apr_status_t json_cleanup(modsec_rec *msr) { assert(msr != NULL); assert(msr->json != NULL); msr_log(msr, 4, "JSON: Cleaning up JSON results"); if (msr->json->handle != NULL) { yajl_free(msr->json->handle); msr->json->handle = NULL; } return 1; } #endif