mirror of
https://github.com/owasp-modsecurity/ModSecurity.git
synced 2025-08-14 05:45:59 +03:00
first pass at JSON logging implementation
This commit is contained in:
parent
4eb095ad25
commit
7b2ca1617e
@ -25,6 +25,11 @@
|
||||
#include "apr_version.h"
|
||||
#include <libxml/xmlversion.h>
|
||||
|
||||
#ifdef WITH_JSON_LOGGING
|
||||
#include <yajl/yajl_gen.h>
|
||||
#include "msc_logging_json.h"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Write the supplied data to the audit log (if the FD is ready), update
|
||||
* the size counters, update the hash context.
|
||||
@ -381,6 +386,40 @@ static void sec_auditlog_write_producer_header(modsec_rec *msr) {
|
||||
sec_auditlog_write(msr, ".\n", 2);
|
||||
}
|
||||
|
||||
#ifdef WITH_JSON_LOGGING
|
||||
/**
|
||||
* Ouput the Producer header into a JSON generator
|
||||
*/
|
||||
static void sec_auditlog_write_producer_header_json(modsec_rec *msr, yajl_gen g) {
|
||||
char **signatures = NULL;
|
||||
int i;
|
||||
|
||||
// this is written no matter what
|
||||
yajl_string(g, "producer");
|
||||
|
||||
/* Try to write verything in one go. */
|
||||
if (msr->txcfg->component_signatures->nelts == 0) {
|
||||
yajl_string(g, MODSEC_MODULE_NAME_FULL);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// we'll need an array if there are component signatures
|
||||
yajl_gen_array_open(g);
|
||||
|
||||
/* Start with the ModSecurity signature. */
|
||||
yajl_string(g, MODSEC_MODULE_NAME_FULL);
|
||||
|
||||
/* Then loop through the components and output individual signatures. */
|
||||
signatures = (char **)msr->txcfg->component_signatures->elts;
|
||||
for(i = 0; i < msr->txcfg->component_signatures->nelts; i++) {
|
||||
yajl_string(g, (char *)signatures[i]);
|
||||
}
|
||||
|
||||
yajl_gen_array_close(g); // array for producers is finished
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* \brief This function will returns the next chain node
|
||||
*
|
||||
@ -480,6 +519,66 @@ static int chained_is_matched(modsec_rec *msr, const msre_rule *next_rule) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef WITH_JSON_LOGGING
|
||||
/**
|
||||
* Write detailed information about a rule and its actionset into a JSON generator
|
||||
*/
|
||||
static void write_rule_json(modsec_rec *msr, const msre_rule *rule, yajl_gen g) {
|
||||
int present = 0;
|
||||
|
||||
yajl_gen_map_open(g);
|
||||
|
||||
yajl_string(g, "actionset");
|
||||
yajl_gen_map_open(g);
|
||||
if (rule->actionset->id) {
|
||||
yajl_kv_string(g, "id", rule->actionset->id);
|
||||
}
|
||||
if (rule->actionset->rev) {
|
||||
yajl_kv_string(g, "rev", rule->actionset->rev);
|
||||
}
|
||||
if (rule->actionset->msg) {
|
||||
yajl_kv_string(g, "msg", rule->actionset->msg);
|
||||
}
|
||||
if (rule->actionset->version) {
|
||||
yajl_kv_string(g, "version", rule->actionset->version);
|
||||
}
|
||||
if (rule->actionset->logdata) {
|
||||
yajl_kv_string(g, "logdata", rule->actionset->logdata);
|
||||
}
|
||||
if (rule->actionset->severity != NOT_SET) {
|
||||
yajl_kv_int(g, "severity", rule->actionset->severity);
|
||||
}
|
||||
if (rule->actionset->accuracy != NOT_SET) {
|
||||
yajl_kv_int(g, "accuracy", rule->actionset->accuracy);
|
||||
}
|
||||
if (rule->actionset->maturity != NOT_SET) {
|
||||
yajl_kv_int(g, "maturity", rule->actionset->maturity);
|
||||
}
|
||||
if (rule->actionset->phase != NOT_SET) {
|
||||
yajl_kv_int(g, "phase", rule->actionset->phase);
|
||||
}
|
||||
yajl_kv_bool(g, "is_chained", rule->actionset->is_chained);
|
||||
yajl_gen_map_close(g);
|
||||
|
||||
yajl_string(g, "operator");
|
||||
yajl_gen_map_open(g);
|
||||
yajl_kv_string(g, "operator", rule->op_name);
|
||||
yajl_kv_string(g, "operator_param", rule->op_param);
|
||||
yajl_kv_string(g, "target", rule->p1);
|
||||
yajl_gen_map_close(g);
|
||||
|
||||
yajl_string(g, "config");
|
||||
yajl_gen_map_open(g);
|
||||
yajl_kv_string(g, "filename", rule->filename);
|
||||
yajl_kv_int(g, "line_num", rule->line_num);
|
||||
yajl_gen_map_close(g);
|
||||
|
||||
yajl_kv_bool(g, "is_matched", chained_is_matched(msr, rule));
|
||||
|
||||
yajl_gen_map_close(g);
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Produce an audit log entry.
|
||||
*/
|
||||
@ -501,9 +600,14 @@ void sec_audit_logger(modsec_rec *msr) {
|
||||
char *buf = NULL, *pat = NULL;
|
||||
msc_parm *mparm = NULL;
|
||||
int arg_min, arg_max, sanitize_matched;
|
||||
#ifdef WITH_JSON_LOGGING
|
||||
yajl_gen g;
|
||||
#endif
|
||||
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
/* the boundary is used by both audit log types */
|
||||
msr->new_auditlog_boundary = create_auditlog_boundary(msr->r);
|
||||
#endif
|
||||
|
||||
/* Return silently if we don't have a request line. This
|
||||
* means we will not be logging request timeouts.
|
||||
@ -591,29 +695,72 @@ void sec_audit_logger(modsec_rec *msr) {
|
||||
}
|
||||
}
|
||||
|
||||
/* AUDITLOG_PART_HEADER */
|
||||
#ifdef WITH_JSON_LOGGING
|
||||
/**
|
||||
* allocate the buffer for the JSON generator
|
||||
* passing null will force yajl to use malloc/realloc/free
|
||||
* need to perf test using APR routines
|
||||
*/
|
||||
g = yajl_gen_alloc(NULL);
|
||||
|
||||
/**
|
||||
* don't pretty print JSON by default
|
||||
* this is harder to eyeball but much easier to parse programmatically
|
||||
*/
|
||||
yajl_gen_config(g, yajl_gen_beautify, 0);
|
||||
|
||||
yajl_gen_map_open(g); // IT BEGINS
|
||||
#endif
|
||||
|
||||
|
||||
/* AUDITLOG_PART_HEADER */
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
text = apr_psprintf(msr->mp, "--%s-%c--\n", msr->new_auditlog_boundary, AUDITLOG_PART_HEADER);
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
|
||||
/* Format: time transaction_id remote_addr remote_port local_addr local_port */
|
||||
|
||||
text = apr_psprintf(msr->mp, "[%s] %s %s %u %s %u",
|
||||
current_logtime(msr->mp), msr->txid, msr->remote_addr, msr->remote_port,
|
||||
msr->local_addr, msr->local_port);
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
#else
|
||||
yajl_string(g, "transaction");
|
||||
yajl_gen_map_open(g); // transaction top-level key
|
||||
|
||||
yajl_kv_string(g, "time", current_logtime(msr->mp));
|
||||
yajl_kv_string(g, "transaction_id", msr->txid);
|
||||
yajl_kv_string(g, "remote_address", msr->remote_addr);
|
||||
yajl_kv_int(g, "remote_port", (int)msr->remote_port); // msr->remote_port is unsigned, yajl wants signed
|
||||
yajl_kv_string(g, "local_address", msr->local_addr);
|
||||
yajl_kv_int(g, "local_port", (int)msr->local_port);
|
||||
|
||||
yajl_gen_map_close(g); // transaction top-level key is finished
|
||||
|
||||
yajl_string(g, "request");
|
||||
yajl_gen_map_open(g); // request top-level key
|
||||
#endif
|
||||
|
||||
/* AUDITLOG_PART_REQUEST_HEADERS */
|
||||
|
||||
if (strchr(msr->txcfg->auditlog_parts, AUDITLOG_PART_REQUEST_HEADERS) != NULL) {
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
text = apr_psprintf(msr->mp, "\n--%s-%c--\n", msr->new_auditlog_boundary, AUDITLOG_PART_REQUEST_HEADERS);
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
#endif
|
||||
|
||||
sanitize_request_line(msr);
|
||||
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
sec_auditlog_write(msr, msr->request_line, strlen(msr->request_line));
|
||||
sec_auditlog_write(msr, "\n", 1);
|
||||
#else
|
||||
// Request Line doesn't get its own map for now. should it?
|
||||
yajl_kv_string(g, "request_line", msr->request_line);
|
||||
#endif
|
||||
|
||||
#ifdef WITH_JSON_LOGGING
|
||||
yajl_string(g, "headers");
|
||||
yajl_gen_map_open(g); // separate map for request headers
|
||||
#endif
|
||||
|
||||
arr = apr_table_elts(msr->request_headers);
|
||||
te = (apr_table_entry_t *)arr->elts;
|
||||
@ -624,7 +771,13 @@ void sec_audit_logger(modsec_rec *msr) {
|
||||
for (i = 0; i < arr->nelts; i++) {
|
||||
sanitized_partial = 0;
|
||||
sanitize_matched = 0;
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
text = apr_psprintf(msr->mp, "%s: %s\n", te[i].key, te[i].val);
|
||||
#else
|
||||
// write the key no matter what
|
||||
// since sanitization only occurs on the value
|
||||
yajl_string(g, te[i].key);
|
||||
#endif
|
||||
if (apr_table_get(msr->request_headers_to_sanitize, te[i].key) != NULL) {
|
||||
buf = apr_psprintf(msr->mp, "%s",text+strlen(te[i].key)+2);
|
||||
|
||||
@ -656,13 +809,30 @@ void sec_audit_logger(modsec_rec *msr) {
|
||||
}
|
||||
|
||||
if(sanitized_partial == 1 && sanitize_matched == 0) {
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
text = apr_psprintf(msr->mp, "%s: %s\n", te[i].key, buf);
|
||||
#else
|
||||
yajl_string(g, buf);
|
||||
#endif
|
||||
} else {
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
memset(text + strlen(te[i].key) + 2, '*', strlen(te[i].val));
|
||||
#else
|
||||
yajl_string(g, "****"); // fix this later
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
#else
|
||||
// we diverge from the original logic a bit because we always print the key
|
||||
// at this no point sanitization had occured, so we just print the value
|
||||
yajl_string(g, te[i].val);
|
||||
#endif
|
||||
}
|
||||
#ifdef WITH_JSON_LOGGING
|
||||
yajl_gen_map_close(g); // request headers map is finished
|
||||
#endif
|
||||
}
|
||||
|
||||
/* AUDITLOG_PART_REQUEST_BODY */
|
||||
@ -749,9 +919,13 @@ void sec_audit_logger(modsec_rec *msr) {
|
||||
unsigned int chunk_offset = 0;
|
||||
unsigned int sanitize_offset = 0;
|
||||
unsigned int sanitize_length = 0;
|
||||
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
text = apr_psprintf(msr->mp, "\n--%s-%c--\n", msr->new_auditlog_boundary, AUDITLOG_PART_REQUEST_BODY);
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
#else
|
||||
yajl_string(g, "body");
|
||||
yajl_gen_array_open(g); // use an array here because we're writing in chunks
|
||||
#endif
|
||||
|
||||
for(;;) {
|
||||
rc = modsecurity_request_body_retrieve(msr, &chunk, -1, &my_error_msg);
|
||||
@ -810,7 +984,11 @@ void sec_audit_logger(modsec_rec *msr) {
|
||||
|
||||
/* Write the sanitized chunk to the log
|
||||
* and advance to the next chunk. */
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
sec_auditlog_write(msr, chunk->data, chunk->length);
|
||||
#else
|
||||
yajl_string(g, chunk->data);
|
||||
#endif
|
||||
chunk_offset += chunk->length;
|
||||
}
|
||||
|
||||
@ -819,6 +997,10 @@ void sec_audit_logger(modsec_rec *msr) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef WITH_JSON_LOGGING
|
||||
yajl_gen_array_close(g); // request body chunks array is finished
|
||||
#endif
|
||||
|
||||
if (rc < 0) {
|
||||
msr_log(msr, 1, "Audit log: %s", my_error_msg);
|
||||
}
|
||||
@ -838,29 +1020,58 @@ void sec_audit_logger(modsec_rec *msr) {
|
||||
if (buffer == NULL) {
|
||||
msr_log(msr, 1, "Audit log: Failed to reconstruct request body.");
|
||||
} else {
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
text = apr_psprintf(msr->mp, "\n--%s-%c--\n", msr->new_auditlog_boundary, AUDITLOG_PART_FAKE_REQUEST_BODY);
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
sec_auditlog_write(msr, buffer, strlen(buffer));
|
||||
#else
|
||||
// this is a key instead 'request', doesn't need an array or map since it's one value
|
||||
yajl_kv_string(g, "fake_body", buffer);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef WITH_JSON_LOGGING
|
||||
yajl_gen_map_close(g); // request top-level key is finished
|
||||
|
||||
yajl_string(g, "response");
|
||||
yajl_gen_map_open(g); // response top-level key
|
||||
#endif
|
||||
|
||||
/* AUDITLOG_PART_A_RESPONSE_HEADERS */
|
||||
|
||||
if (strchr(msr->txcfg->auditlog_parts, AUDITLOG_PART_A_RESPONSE_HEADERS) != NULL) {
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
text = apr_psprintf(msr->mp, "\n--%s-%c--\n", msr->new_auditlog_boundary, AUDITLOG_PART_A_RESPONSE_HEADERS);
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
#endif
|
||||
|
||||
/* There are no response headers (or the status line) in HTTP 0.9 */
|
||||
if (msr->response_headers_sent) {
|
||||
if (msr->status_line != NULL) {
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
text = apr_psprintf(msr->mp, "%s %s\n", msr->response_protocol,
|
||||
msr->status_line);
|
||||
#else
|
||||
yajl_kv_string(g, "protocol", msr->response_protocol);
|
||||
yajl_kv_string(g, "status", msr->status_line);
|
||||
#endif
|
||||
} else {
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
text = apr_psprintf(msr->mp, "%s %u\n", msr->response_protocol,
|
||||
msr->response_status);
|
||||
#else
|
||||
yajl_kv_string(g, "protocol", msr->response_protocol);
|
||||
yajl_kv_int(g, "status", (int)msr->response_status);
|
||||
#endif
|
||||
}
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
#else
|
||||
yajl_string(g, "headers");
|
||||
yajl_gen_map_open(g); // separate map for response headers
|
||||
#endif
|
||||
|
||||
/* Output headers */
|
||||
|
||||
@ -873,7 +1084,13 @@ void sec_audit_logger(modsec_rec *msr) {
|
||||
for (i = 0; i < arr->nelts; i++) {
|
||||
sanitized_partial = 0;
|
||||
sanitize_matched = 0;
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
text = apr_psprintf(msr->mp, "%s: %s\n", te[i].key, te[i].val);
|
||||
#else
|
||||
// write the key no matter what
|
||||
// since sanitization only occurs on the value
|
||||
yajl_string(g, te[i].key);
|
||||
#endif
|
||||
if (apr_table_get(msr->response_headers_to_sanitize, te[i].key) != NULL) {
|
||||
buf = apr_psprintf(msr->mp, "%s",text+strlen(te[i].key)+2);
|
||||
|
||||
@ -905,13 +1122,30 @@ void sec_audit_logger(modsec_rec *msr) {
|
||||
}
|
||||
|
||||
if(sanitized_partial == 1 && sanitize_matched == 0) {
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
text = apr_psprintf(msr->mp, "%s: %s\n", te[i].key, buf);
|
||||
#else
|
||||
yajl_string(g, buf);
|
||||
#endif
|
||||
} else {
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
memset(text + strlen(te[i].key) + 2, '*', strlen(te[i].val));
|
||||
#else
|
||||
yajl_string(g, "****"); // fix this later
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
#else
|
||||
// we diverge from the original logic a bit because we always print the key
|
||||
// at this point no sanitization had occured, so we just print the value
|
||||
yajl_string(g, te[i].val);
|
||||
#endif
|
||||
}
|
||||
#ifdef WITH_JSON_LOGGING
|
||||
yajl_gen_map_close(g); // response headers map is finised
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@ -921,51 +1155,101 @@ void sec_audit_logger(modsec_rec *msr) {
|
||||
|
||||
if (strchr(msr->txcfg->auditlog_parts, AUDITLOG_PART_RESPONSE_BODY) != NULL) {
|
||||
if (msr->resbody_data != NULL) {
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
text = apr_psprintf(msr->mp, "\n--%s-%c--\n", msr->new_auditlog_boundary, AUDITLOG_PART_RESPONSE_BODY);
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
sec_auditlog_write(msr, msr->resbody_data, msr->resbody_length);
|
||||
#else
|
||||
yajl_kv_string(g, "body", msr->resbody_data);
|
||||
#endif
|
||||
wrote_response_body = 1;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef WITH_JSON_LOGGING
|
||||
yajl_gen_map_close(g); // response top-level key is finished
|
||||
|
||||
yajl_string(g, "data");
|
||||
yajl_gen_map_open(g); // data top-level key
|
||||
#endif
|
||||
|
||||
/* AUDITLOG_PART_TRAILER */
|
||||
|
||||
if (strchr(msr->txcfg->auditlog_parts, AUDITLOG_PART_TRAILER) != NULL) {
|
||||
apr_time_t now = apr_time_now();
|
||||
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
text = apr_psprintf(msr->mp, "\n--%s-%c--\n", msr->new_auditlog_boundary, AUDITLOG_PART_TRAILER);
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
#endif
|
||||
|
||||
/* Messages */
|
||||
#ifdef WITH_JSON_LOGGING
|
||||
yajl_string(g, "messages");
|
||||
yajl_gen_array_open(g);
|
||||
#endif
|
||||
for(i = 0; i < msr->alerts->nelts; i++) {
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
text = apr_psprintf(msr->mp, "Message: %s\n", ((char **)msr->alerts->elts)[i]);
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
#else
|
||||
yajl_string(g, ((char **)msr->alerts->elts)[i]);
|
||||
#endif
|
||||
}
|
||||
#ifdef WITH_JSON_LOGGING
|
||||
yajl_gen_array_close(g);
|
||||
#endif
|
||||
|
||||
/* Apache error messages */
|
||||
#ifdef WITH_JSON_LOGGING
|
||||
yajl_string(g, "error_messages");
|
||||
yajl_gen_array_open(g);
|
||||
#endif
|
||||
for(i = 0; i < msr->error_messages->nelts; i++) {
|
||||
error_message_t *em = (((error_message_t **)msr->error_messages->elts)[i]);
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
text = apr_psprintf(msr->mp, "Apache-Error: %s\n",
|
||||
format_error_log_message(msr->mp, em));
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
#else
|
||||
yajl_string(g, format_error_log_message(msr->mp, em));
|
||||
#endif
|
||||
}
|
||||
#ifdef WITH_JSON_LOGGING
|
||||
yajl_gen_array_close(g);
|
||||
#endif
|
||||
|
||||
/* Action */
|
||||
if (msr->was_intercepted) {
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
text = apr_psprintf(msr->mp, "Action: Intercepted (phase %d)\n", msr->intercept_phase);
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
#else
|
||||
yajl_string(g, "action");
|
||||
yajl_gen_map_open(g);
|
||||
yajl_kv_bool(g, "intercepted", 1);
|
||||
yajl_kv_int(g, "phase", msr->intercept_phase);
|
||||
yajl_gen_map_close(g);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Apache-Handler */
|
||||
if (msr->r->handler != NULL) {
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
text = apr_psprintf(msr->mp, "Apache-Handler: %s\n", msr->r->handler);
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
#else
|
||||
yajl_kv_string(g, "handler", msr->r->handler);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Stopwatch; left in for compatibility reasons */
|
||||
text = apr_psprintf(msr->mp, "Stopwatch: %" APR_TIME_T_FMT " %" APR_TIME_T_FMT " (- - -)\n",
|
||||
msr->request_time, (now - msr->request_time));
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
#else
|
||||
yajl_kv_string(g, "stopwatch", text);
|
||||
#endif
|
||||
|
||||
/* Stopwatch2 */
|
||||
{
|
||||
@ -973,26 +1257,46 @@ void sec_audit_logger(modsec_rec *msr) {
|
||||
|
||||
text = apr_psprintf(msr->mp, "Stopwatch2: %" APR_TIME_T_FMT " %" APR_TIME_T_FMT
|
||||
"; %s\n", msr->request_time, (now - msr->request_time), perf_all);
|
||||
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
#else
|
||||
yajl_kv_string(g, "stopwatch2", text);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Our response body does not contain chunks */
|
||||
/* ENH Only write this when the output was chunked. */
|
||||
/* ENH Add info when request body was decompressed, dechunked too. */
|
||||
if (wrote_response_body) {
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
text = apr_psprintf(msr->mp, "Response-Body-Transformed: Dechunked\n");
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
#else
|
||||
yajl_kv_bool(g, "response_body_dechunked", 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
sec_auditlog_write_producer_header(msr);
|
||||
#else
|
||||
sec_auditlog_write_producer_header_json(msr, g);
|
||||
#endif
|
||||
|
||||
/* Server */
|
||||
if (msr->server_software != NULL) {
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
text = apr_psprintf(msr->mp, "Server: %s\n", msr->server_software);
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
#else
|
||||
yajl_kv_string(g, "server", msr->server_software);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef WITH_JSON_LOGGING
|
||||
yajl_string(g, "sanitized");
|
||||
yajl_gen_map_open(g); // open a separate map for sanitized values
|
||||
#endif
|
||||
|
||||
/* Sanitised arguments */
|
||||
{
|
||||
const apr_array_header_t *tarr;
|
||||
@ -1002,16 +1306,31 @@ void sec_audit_logger(modsec_rec *msr) {
|
||||
telts = (const apr_table_entry_t*)tarr->elts;
|
||||
|
||||
if (tarr->nelts > 0) {
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
text = apr_psprintf(msr->mp, "Sanitised-Args: ");
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
#else
|
||||
yajl_string(g, "args");
|
||||
yajl_gen_array_open(g);
|
||||
#endif
|
||||
}
|
||||
|
||||
for(i = 0; i < tarr->nelts; i++) {
|
||||
msc_arg *arg = (msc_arg *)telts[i].val;
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
text = apr_psprintf(msr->mp, "%s\"%s\"%s", ((i == 0) ? "" : ", "),
|
||||
log_escape(msr->mp, arg->name), ((i == (tarr->nelts - 1)) ? ".\n" : ""));
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
#else
|
||||
// yay arrays actually make it easier here
|
||||
yajl_string(g, log_escape(msr->mp, arg->name));
|
||||
#endif
|
||||
}
|
||||
#ifdef WITH_JSON_LOGGING
|
||||
if (tarr->nelts > 0) {
|
||||
yajl_gen_array_close(g);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Sanitised request headers */
|
||||
@ -1023,15 +1342,29 @@ void sec_audit_logger(modsec_rec *msr) {
|
||||
telts = (const apr_table_entry_t*)tarr->elts;
|
||||
|
||||
if (tarr->nelts > 0) {
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
text = apr_psprintf(msr->mp, "Sanitised-Request-Headers: ");
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
#else
|
||||
yajl_string(g, "request_headers");
|
||||
yajl_gen_array_open(g);
|
||||
#endif
|
||||
}
|
||||
|
||||
for(i = 0; i < tarr->nelts; i++) {
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
text = apr_psprintf(msr->mp, "%s\"%s\"%s", ((i == 0) ? "" : ", "),
|
||||
log_escape(msr->mp, telts[i].key), ((i == (tarr->nelts - 1)) ? ".\n" : ""));
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
#else
|
||||
yajl_string(g, log_escape(msr->mp, telts[i].key));
|
||||
#endif
|
||||
}
|
||||
#ifdef WITH_JSON_LOGGING
|
||||
if (tarr->nelts > 0) {
|
||||
yajl_gen_array_close(g);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Sanitised response headers */
|
||||
@ -1043,40 +1376,85 @@ void sec_audit_logger(modsec_rec *msr) {
|
||||
telts = (const apr_table_entry_t*)tarr->elts;
|
||||
|
||||
if (tarr->nelts > 0) {
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
text = apr_psprintf(msr->mp, "Sanitised-Response-Headers: ");
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
#else
|
||||
yajl_string(g, "response_headers");
|
||||
yajl_gen_array_open(g);
|
||||
#endif
|
||||
}
|
||||
|
||||
for(i = 0; i < tarr->nelts; i++) {
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
text = apr_psprintf(msr->mp, "%s\"%s\"%s", ((i == 0) ? "" : ", "),
|
||||
log_escape(msr->mp, telts[i].key), ((i == (tarr->nelts - 1)) ? ".\n" : ""));
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
#else
|
||||
yajl_string(g, log_escape(msr->mp, telts[i].key));
|
||||
#endif
|
||||
}
|
||||
#ifdef WITH_JSON_LOGGING
|
||||
if (tarr->nelts > 0) {
|
||||
yajl_gen_array_close(g);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef WITH_JSON_LOGGING
|
||||
yajl_gen_map_close(g); // sanitized args map is finished
|
||||
#endif
|
||||
|
||||
/* Web application info. */
|
||||
if ( ((msr->txcfg->webappid != NULL)&&(strcmp(msr->txcfg->webappid, "default") != 0))
|
||||
|| (msr->sessionid != NULL) || (msr->userid != NULL))
|
||||
{
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
text = apr_psprintf(msr->mp, "WebApp-Info: \"%s\" \"%s\" \"%s\"\n",
|
||||
msr->txcfg->webappid == NULL ? "-" : log_escape(msr->mp, msr->txcfg->webappid),
|
||||
msr->sessionid == NULL ? "-" : log_escape(msr->mp, msr->sessionid),
|
||||
msr->userid == NULL ? "-" : log_escape(msr->mp, msr->userid));
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
#else
|
||||
yajl_string(g, "webapp_info");
|
||||
yajl_gen_map_open(g);
|
||||
|
||||
if (msr->txcfg->webappid != NULL) {
|
||||
yajl_kv_string(g, "id", log_escape(msr->mp, msr->txcfg->webappid));
|
||||
}
|
||||
if (msr->sessionid != NULL) {
|
||||
yajl_kv_string(g, "session", log_escape(msr->mp, msr->sessionid));
|
||||
}
|
||||
if (msr->userid != NULL) {
|
||||
yajl_kv_string(g, "user_id", log_escape(msr->mp, msr->userid));
|
||||
}
|
||||
|
||||
yajl_gen_map_close(g);
|
||||
#endif
|
||||
}
|
||||
|
||||
if ( ((msr->txcfg->sensor_id != NULL)&&(strcmp(msr->txcfg->sensor_id, "default") != 0)))
|
||||
{
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
text = apr_psprintf(msr->mp, "Sensor-Id: \"%s\"\n",
|
||||
msr->txcfg->sensor_id == NULL ? "-" : log_escape(msr->mp, msr->txcfg->sensor_id)),
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
#else
|
||||
if(msr->txcfg->sensor_id != NULL) {
|
||||
yajl_kv_string(g, "sensor_id", log_escape(msr->mp, msr->txcfg->sensor_id));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
if (msr->txcfg->is_enabled > 0) {
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
text = apr_psprintf(msr->mp, "Engine-Mode: \"%s\"\n",
|
||||
msr->txcfg->is_enabled == 1 ? "DETECTION_ONLY" : "ENABLED"),
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
#else
|
||||
yajl_kv_string(g, "engine_mode", (msr->txcfg->is_enabled == 1 ? "DETECTION_ONLY" : "ENABLED"));
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Rule performance time */
|
||||
@ -1088,54 +1466,104 @@ void sec_audit_logger(modsec_rec *msr) {
|
||||
telts = (const apr_table_entry_t*)tarr->elts;
|
||||
|
||||
if (tarr->nelts > 0) {
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
text = apr_psprintf(msr->mp, "Rules-Performance-Info: ");
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
#else
|
||||
yajl_string(g, "rules_performance_info");
|
||||
yajl_gen_map_open(g); // separate map for rule perf info
|
||||
#endif
|
||||
}
|
||||
|
||||
for(i = 0; i < tarr->nelts; i++) {
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
text = apr_psprintf(msr->mp, "%s\"%s=%s\"%s", ((i == 0) ? "" : ", "),
|
||||
log_escape(msr->mp, telts[i].key), log_escape(msr->mp, telts[i].val), ((i == (tarr->nelts - 1)) ? ".\n" : ""));
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
#else
|
||||
yajl_kv_string(g, log_escape(msr->mp, telts[i].key), log_escape(msr->mp, telts[i].val));
|
||||
#endif
|
||||
}
|
||||
#ifdef WITH_JSON_LOGGING
|
||||
if (tarr->nelts > 0) {
|
||||
yajl_gen_map_close(g); // map for rule perf info is finished
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#ifdef WITH_JSON_LOGGING
|
||||
yajl_gen_map_close(g); // data top-level key is finished
|
||||
#endif
|
||||
|
||||
/* AUDITLOG_PART_UPLOADS */
|
||||
if ((strchr(msr->txcfg->auditlog_parts, AUDITLOG_PART_UPLOADS) != NULL) && (msr->mpd != NULL)) {
|
||||
multipart_part **parts = NULL;
|
||||
unsigned int total_size = 0;
|
||||
int cfiles = 0;
|
||||
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
text = apr_psprintf(msr->mp, "\n--%s-%c--\n", msr->new_auditlog_boundary, AUDITLOG_PART_UPLOADS);
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
#else
|
||||
yajl_string(g, "uploads");
|
||||
yajl_gen_map_open(g);
|
||||
#endif
|
||||
|
||||
parts = (multipart_part **)msr->mpd->parts->elts;
|
||||
#ifdef WITH_JSON_LOGGING
|
||||
yajl_string(g, "info");
|
||||
yajl_gen_array_open(g); // separate array for upload info
|
||||
#endif
|
||||
for(cfiles = 0; cfiles < msr->mpd->parts->nelts; cfiles++) {
|
||||
if (parts[cfiles]->type == MULTIPART_FILE) {
|
||||
if(parts[cfiles]->filename != NULL) {
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
text = apr_psprintf(msr->mp, "%d,%u,\"%s\",\"%s\"\n", cfiles+1, parts[cfiles]->tmp_file_size, log_escape(msr->mp, parts[cfiles]->filename), log_escape(msr->mp, parts[cfiles]->content_type ? parts[cfiles]->content_type : "<Unknown ContentType>"));
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
#else
|
||||
yajl_kv_int(g, "file_size", parts[cfiles]->tmp_file_size);
|
||||
yajl_kv_string(g, "file_name", log_escape(msr->mp, parts[cfiles]->filename));
|
||||
yajl_kv_string(g, "content_type", parts[cfiles]->content_type ? parts[cfiles]->content_type : "<Unknown Content-Type>");
|
||||
#endif
|
||||
total_size += parts[cfiles]->tmp_file_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
text = apr_psprintf(msr->mp, "Total,%u\n", total_size);
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
#else
|
||||
yajl_gen_array_close(g); // array for upload info is finished
|
||||
yajl_kv_int(g, "total", total_size);
|
||||
|
||||
yajl_gen_map_close(g); // uploads top-level key is finished
|
||||
#endif
|
||||
}
|
||||
|
||||
/* AUDITLOG_PART_MATCHEDRULES */
|
||||
|
||||
if (strchr(msr->txcfg->auditlog_parts, AUDITLOG_PART_MATCHEDRULES) != NULL) {
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
text = apr_psprintf(msr->mp, "\n--%s-%c--\n", msr->new_auditlog_boundary, AUDITLOG_PART_MATCHEDRULES);
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
#else
|
||||
yajl_string(g, "matched_rules");
|
||||
yajl_gen_array_open(g); // matched_rules top-level key
|
||||
#endif
|
||||
|
||||
|
||||
/* Matched Rules */
|
||||
|
||||
for(i = 0; i < msr->matched_rules->nelts; i++) {
|
||||
rule = ((msre_rule **)msr->matched_rules->elts)[i];
|
||||
if ((rule != NULL) && (rule->actionset != NULL) && rule->actionset->is_chained && (rule->chain_starter == NULL)) {
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
text = apr_psprintf(msr->mp, "%s\n", rule->unparsed);
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
#else
|
||||
write_rule_json(msr, rule, g);
|
||||
#endif
|
||||
do {
|
||||
if (rule->ruleset != NULL) {
|
||||
|
||||
@ -1146,32 +1574,59 @@ void sec_audit_logger(modsec_rec *msr) {
|
||||
present = chained_is_matched(msr,next_rule);
|
||||
|
||||
if (present == 0) {
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
text = apr_psprintf(msr->mp, "#%s\n",next_rule->unparsed);
|
||||
#endif
|
||||
} else {
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
text = apr_psprintf(msr->mp, "%s\n",next_rule->unparsed);
|
||||
#endif
|
||||
i++;
|
||||
}
|
||||
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
#else
|
||||
write_rule_json(msr, next_rule, g);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
rule = next_rule;
|
||||
} while (rule != NULL && rule->actionset != NULL && rule->actionset->is_chained);
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
text = apr_psprintf(msr->mp, "\n");
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
#endif
|
||||
} else {
|
||||
if ((rule != NULL) && (rule->actionset != NULL) && !rule->actionset->is_chained && (rule->chain_starter == NULL)) {
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
text = apr_psprintf(msr->mp, "%s\n\n", rule->unparsed);
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
#else
|
||||
write_rule_json(msr, rule, g);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
#ifdef WITH_JSON_LOGGING
|
||||
yajl_gen_array_close(g); // matched_rules top-level key is finished
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
/* AUDITLOG_PART_ENDMARKER */
|
||||
|
||||
#ifndef WITH_JSON_LOGGING
|
||||
text = apr_psprintf(msr->mp, "\n--%s-%c--\n", msr->new_auditlog_boundary, AUDITLOG_PART_ENDMARKER);
|
||||
sec_auditlog_write(msr, text, strlen(text));
|
||||
#else
|
||||
yajl_gen_map_close(g); // box it up!
|
||||
|
||||
const unsigned char *final_buf;
|
||||
size_t len;
|
||||
yajl_gen_get_buf(g, &final_buf, &len);
|
||||
sec_auditlog_write(msr, final_buf, len);
|
||||
|
||||
yajl_gen_clear(g);
|
||||
yajl_gen_free(g);
|
||||
#endif
|
||||
|
||||
/* Return here if we were writing to a serial log
|
||||
* as it does not need an index file.
|
||||
|
13
apache2/msc_logging_json.h
Normal file
13
apache2/msc_logging_json.h
Normal file
@ -0,0 +1,13 @@
|
||||
#include <string.h>
|
||||
|
||||
#include <yajl/yajl_gen.h>
|
||||
|
||||
#define yajl_string(g, s) yajl_gen_string(g, (const unsigned char *)s, strlen(s))
|
||||
|
||||
#define yajl_kv_null(g, k) yajl_string(g, k); yajl_gen_null(g)
|
||||
|
||||
#define yajl_kv_int(g, k, v) yajl_string(g, k); yajl_gen_integer(g, v)
|
||||
|
||||
#define yajl_kv_string(g, k, v) yajl_string(g, k); yajl_string(g, v)
|
||||
|
||||
#define yajl_kv_bool(g, k, v) yajl_string(g, k); yajl_gen_bool(g, v)
|
18
configure.ac
18
configure.ac
@ -275,6 +275,21 @@ if test "$build_docs" -eq 1; then
|
||||
TOPLEVEL_SUBDIRS="$TOPLEVEL_SUBDIRS docs"
|
||||
fi
|
||||
|
||||
# Add JSON audit logging
|
||||
AC_ARG_ENABLE(json-logging,
|
||||
AS_HELP_STRING([--enabled-json-logging],
|
||||
[Enable JSON audit logging.]),
|
||||
[
|
||||
if test "$enableval" != "no"; then
|
||||
json_logging='-DWITH_JSON_LOGGING'
|
||||
MODSEC_EXTRA_CFLAGS="$MODSEC_EXTRA_CFLAGS $json_logging"
|
||||
else
|
||||
json_logging=''
|
||||
fi
|
||||
],
|
||||
[
|
||||
json_logging=''
|
||||
])
|
||||
|
||||
# Add PCRE Studying
|
||||
|
||||
@ -659,8 +674,7 @@ else
|
||||
fi
|
||||
fi
|
||||
|
||||
MODSEC_EXTRA_CFLAGS="$pcre_study $pcre_match_limit $pcre_match_limit_recursion $pcre_jit $request_early $htaccess_config $lua_cache $debug_conf $debug_cache $debug_acmp $debug_mem $perf_meas $modsec_api $cpu_type"
|
||||
|
||||
MODSEC_EXTRA_CFLAGS="$json_logging $pcre_study $pcre_match_limit $pcre_match_limit_recursion $pcre_jit $request_early $htaccess_config $lua_cache $debug_conf $debug_cache $debug_acmp $debug_mem $perf_meas $modsec_api $cpu_type"
|
||||
APXS_WRAPPER=build/apxs-wrapper
|
||||
APXS_EXTRA_CFLAGS=""
|
||||
for f in $EXTRA_CFLAGS; do
|
||||
|
Loading…
x
Reference in New Issue
Block a user