Skip to content

JSON Body Processor #24

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apache2/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions apache2/modsecurity.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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) {
Expand Down
2 changes: 2 additions & 0 deletions apache2/modsecurity.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions apache2/modules.mk
Original file line number Diff line number Diff line change
@@ -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}
Expand Down
313 changes: 313 additions & 0 deletions apache2/msc_json.c
Original file line number Diff line number Diff line change
@@ -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;
}

Loading