diff --git a/CHANGES b/CHANGES index 6c249b5d..ab2fd8e2 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,12 @@ ?? ??? 2007 - 2.2.0-trunk ------------------------- + * Added experimental support for content injection. Directive SecContentInjection + (On|Off) controls whether injection is taking place. Actions "prepend" + and "append" inject content when executed. Do note that it is your + responsibility to make sure the response is of the appropriate + content type (e.g. HTML, plain text, etc). + * Added string comparison operators with support for macro expansion: @contains, @is, @beginsWith and @endsWith. diff --git a/apache2/apache2_config.c b/apache2/apache2_config.c index 0642109b..3d741553 100644 --- a/apache2/apache2_config.c +++ b/apache2/apache2_config.c @@ -81,6 +81,9 @@ void *create_directory_config(apr_pool_t *mp, char *path) { dcfg->data_dir = NOT_SET_P; dcfg->webappid = NOT_SET_P; + /* Content injection. */ + dcfg->content_injection_enabled = NOT_SET; + return dcfg; } @@ -356,6 +359,10 @@ void *merge_directory_configs(apr_pool_t *mp, void *_parent, void *_child) { merged->webappid = (child->webappid == NOT_SET_P ? parent->webappid : child->webappid); + /* Content injection. */ + merged->content_injection_enabled = (child->content_injection_enabled == NOT_SET + ? parent->content_injection_enabled : child->content_injection_enabled); + return merged; } @@ -414,6 +421,9 @@ void init_directory_config(directory_config *dcfg) { /* Misc */ if (dcfg->data_dir == NOT_SET_P) dcfg->data_dir = NULL; if (dcfg->webappid == NOT_SET_P) dcfg->webappid = "default"; + + /* Content injection. */ + if (dcfg->content_injection_enabled == NOT_SET) dcfg->content_injection_enabled = 0; } /** @@ -696,6 +706,13 @@ static const char *cmd_chroot_dir(cmd_parms *cmd, void *_dcfg, const char *p1) { return NULL; } +static const char *cmd_content_injection(cmd_parms *cmd, void *_dcfg, int flag) { + directory_config *dcfg = (directory_config *)_dcfg; + if (dcfg == NULL) return NULL; + dcfg->content_injection_enabled = flag; + return NULL; +} + static const char *cmd_data_dir(cmd_parms *cmd, void *_dcfg, const char *p1) { directory_config *dcfg = (directory_config *)_dcfg; @@ -1175,6 +1192,14 @@ const command_rec module_directives[] = { "Path of the directory to which server will be chrooted" ), + AP_INIT_FLAG ( + "SecContentInjection", + cmd_content_injection, + NULL, + CMD_SCOPE_ANY, + "On or Off" + ), + AP_INIT_TAKE1 ( "SecCookieFormat", cmd_cookie_format, diff --git a/apache2/apache2_io.c b/apache2/apache2_io.c index 3221047a..551310d0 100644 --- a/apache2/apache2_io.c +++ b/apache2/apache2_io.c @@ -384,7 +384,7 @@ static apr_status_t output_filter_init(modsec_rec *msr, ap_filter_t *f, apr_status_t output_filter(ap_filter_t *f, apr_bucket_brigade *bb_in) { request_rec *r = f->r; modsec_rec *msr = (modsec_rec *)f->ctx; - apr_bucket *bucket; + apr_bucket *bucket = NULL, *eos_bucket = NULL; apr_status_t rc; if (msr == NULL) { @@ -445,6 +445,40 @@ apr_status_t output_filter(ap_filter_t *f, apr_bucket_brigade *bb_in) { /* Continue (observe the response body). */ break; } + + // If injecting content unset headers now. + if (msr->txcfg->content_injection_enabled == 0) { + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Content Injection: Not enabled."); + } + } else { + if ((msr->content_prepend) || (msr->content_append)) { + apr_table_unset(msr->r->headers_out, "Content-Length"); + apr_table_unset(msr->r->headers_out, "Last-Modified"); + apr_table_unset(msr->r->headers_out, "ETag"); + apr_table_unset(msr->r->headers_out, "Expires"); + + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Content Injection: Removing headers (C-L, L-M, Etag, Expires)."); + } + } else { + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Content Injection: Nothing to inject."); + } + } + } + + // Content injection (prepend & non-buffering). + if (msr->txcfg->content_injection_enabled && msr->content_prepend) { + apr_bucket *bucket_ci = apr_bucket_heap_create(msr->content_prepend, + msr->content_prepend_len, NULL, f->r->connection->bucket_alloc); + APR_BRIGADE_INSERT_HEAD(bb_in, bucket_ci); + + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Content Injection (nb): Added content to top: %s", + log_escape_nq_ex(msr->mp, msr->content_prepend, msr->content_prepend_len)); + } + } } else if (msr->of_status == OF_STATUS_COMPLETE) { msr_log(msr, 1, "Output filter: Internal error: output filtering complete yet filter was invoked."); @@ -452,6 +486,7 @@ apr_status_t output_filter(ap_filter_t *f, apr_bucket_brigade *bb_in) { return APR_EGENERAL; } + /* Loop through the buckets in the brigade in order * to extract the size of the data available. */ @@ -490,6 +525,22 @@ apr_status_t output_filter(ap_filter_t *f, apr_bucket_brigade *bb_in) { } if (APR_BUCKET_IS_EOS(bucket)) { + eos_bucket = bucket; + + // Inject content (append & non-buffering). + if (msr->txcfg->content_injection_enabled && msr->content_append) { + apr_bucket *bucket_ci = NULL; + + bucket_ci = apr_bucket_heap_create(msr->content_append, + msr->content_append_len, NULL, f->r->connection->bucket_alloc); + APR_BUCKET_INSERT_BEFORE(bucket, bucket_ci); + + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Content-Injection (nb): Added content to bottom: %s", + log_escape_nq_ex(msr->mp, msr->content_append, msr->content_append_len)); + } + } + msr->of_done_reading = 1; } } @@ -541,7 +592,6 @@ apr_status_t output_filter(ap_filter_t *f, apr_bucket_brigade *bb_in) { } // TODO Why does the function below take pointer to length? Will it modify it? - rc = apr_brigade_flatten(msr->of_brigade, msr->resbody_data, &msr->resbody_length); if (rc != APR_SUCCESS) { msr_log(msr, 1, "Output filter: Failed to flatten brigade (%i): %s", rc, @@ -567,6 +617,34 @@ apr_status_t output_filter(ap_filter_t *f, apr_bucket_brigade *bb_in) { if (msr->of_skipping == 0) { record_time_checkpoint(msr, 3); + // Inject content into response (prepend & buffering). + if (msr->txcfg->content_injection_enabled && msr->content_prepend) { + apr_bucket *bucket_ci = NULL; + + bucket_ci = apr_bucket_heap_create(msr->content_prepend, + msr->content_prepend_len, NULL, f->r->connection->bucket_alloc); + APR_BRIGADE_INSERT_HEAD(msr->of_brigade, bucket_ci); + + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Content Injection (b): Added content to top: %s", + log_escape_nq_ex(msr->mp, msr->content_prepend, msr->content_prepend_len)); + } + } + + // Inject content into response (append & buffering). + if (msr->txcfg->content_injection_enabled && msr->content_append) { + apr_bucket *bucket_ci = NULL; + + bucket_ci = apr_bucket_heap_create(msr->content_append, + msr->content_append_len, NULL, f->r->connection->bucket_alloc); + APR_BUCKET_INSERT_BEFORE(eos_bucket, bucket_ci); + + if (msr->txcfg->debuglog_level >= 9) { + msr_log(msr, 9, "Content-Injection (b): Added content to bottom: %s", + log_escape_nq_ex(msr->mp, msr->content_append, msr->content_append_len)); + } + } + rc = ap_pass_brigade(f->next, msr->of_brigade); if (rc != APR_SUCCESS) { int log_level = 1; diff --git a/apache2/modsecurity.h b/apache2/modsecurity.h index f93faa26..6bfadf04 100644 --- a/apache2/modsecurity.h +++ b/apache2/modsecurity.h @@ -309,6 +309,12 @@ struct modsec_rec { /* rule processing temp pool */ apr_pool_t *msc_rule_mptmp; + + /* content injection */ + const char *content_prepend; + apr_off_t content_prepend_len; + const char *content_append; + apr_off_t content_append_len; }; struct directory_config { @@ -386,6 +392,9 @@ struct directory_config { /* Misc */ const char *data_dir; const char *webappid; + + /* Content injection. */ + int content_injection_enabled; }; struct error_message { diff --git a/apache2/re_actions.c b/apache2/re_actions.c index 5d66bbad..6062f12b 100644 --- a/apache2/re_actions.c +++ b/apache2/re_actions.c @@ -1363,6 +1363,26 @@ static apr_status_t msre_action_exec_execute(modsec_rec *msr, apr_pool_t *mptmp, return 1; } +/* prepend */ +static apr_status_t msre_action_prepend_execute(modsec_rec *msr, apr_pool_t *mptmp, + msre_rule *rule, msre_action *action) +{ + msr->content_prepend = action->param; + msr->content_prepend_len = strlen(action->param); + + return 1; +} + +/* append */ +static apr_status_t msre_action_append_execute(modsec_rec *msr, apr_pool_t *mptmp, + msre_rule *rule, msre_action *action) +{ + msr->content_append = action->param; + msr->content_append_len = strlen(action->param); + + return 1; +} + /* -- */ /** @@ -1813,4 +1833,28 @@ void msre_engine_register_default_actions(msre_engine *engine) { NULL, NULL ); + + /* prepend */ + msre_engine_action_register(engine, + "prepend", + ACTION_NON_DISRUPTIVE, + 1, 1, + NO_PLUS_MINUS, + ACTION_CARDINALITY_ONE, + NULL, + NULL, + msre_action_prepend_execute + ); + + /* append */ + msre_engine_action_register(engine, + "append", + ACTION_NON_DISRUPTIVE, + 1, 1, + NO_PLUS_MINUS, + ACTION_CARDINALITY_ONE, + NULL, + NULL, + msre_action_append_execute + ); }