/* * ModSecurity for Apache 2.x, http://www.modsecurity.org/ * Copyright (c) 2004-2013 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 #include #include "http_core.h" #include "http_request.h" #include "modsecurity.h" #include "apache2.h" #include "http_main.h" #include "http_connection.h" #include "apr_optional.h" #include "mod_log_config.h" #include "msc_logging.h" #include "msc_util.h" #include "ap_mpm.h" #include "scoreboard.h" #include "apr_version.h" #include "apr_lib.h" #include "ap_config.h" #include "http_config.h" #include "api.h" #ifdef WIN32 #include "msc_status_engine.h" #endif extern void *modsecLogObj; extern void (*modsecLogHook)(void *obj, int level, char *str); extern int (*modsecDropAction)(request_rec *r); apr_status_t (*modsecReadBody)(request_rec *r, char *buf, unsigned int length, unsigned int *readcnt, int *is_eos); apr_status_t (*modsecReadResponse)(request_rec *r, char *buf, unsigned int length, unsigned int *readcnt, int *is_eos); apr_status_t (*modsecWriteBody)(request_rec *r, char *buf, unsigned int length); apr_status_t (*modsecWriteResponse)(request_rec *r, char *buf, unsigned int length); extern const char *process_command_config(server_rec *s, void *mconfig, apr_pool_t *p, apr_pool_t *ptemp, const char *filename); #define DECLARE_EXTERNAL_HOOK(ns,link,ret,name,args) \ extern ns##_HOOK_##name##_t *hookfn_##name; #define DECLARE_HOOK(ret,name,args) \ DECLARE_EXTERNAL_HOOK(ap,AP,ret,name,args) DECLARE_HOOK(int,pre_config,(apr_pool_t *pconf,apr_pool_t *plog, apr_pool_t *ptemp)) DECLARE_HOOK(int,post_config,(apr_pool_t *pconf,apr_pool_t *plog, apr_pool_t *ptemp,server_rec *s)) DECLARE_HOOK(void,child_init,(apr_pool_t *pchild, server_rec *s)) DECLARE_HOOK(int,process_connection,(conn_rec *c)) DECLARE_HOOK(int,post_read_request,(request_rec *r)) DECLARE_HOOK(int,fixups,(request_rec *r)) DECLARE_HOOK(void, error_log, (const char *file, int line, int level, apr_status_t status, const server_rec *s, const request_rec *r, apr_pool_t *pool, const char *errstr)) DECLARE_HOOK(int,log_transaction,(request_rec *r)) DECLARE_HOOK(void,insert_filter,(request_rec *r)) DECLARE_HOOK(void,insert_error_filter,(request_rec *r)) char *sa_name = "standalone"; const char *sa_name_argv[] = { "standalone", NULL }; server_rec *server; apr_pool_t *pool = NULL; apr_status_t ap_http_in_filter(ap_filter_t *f, apr_bucket_brigade *b, ap_input_mode_t mode, apr_read_type_e block, apr_off_t readbytes); apr_status_t ap_http_out_filter(ap_filter_t *f, apr_bucket_brigade *b); server_rec *modsecInit() { apr_initialize(); apr_pool_create(&pool, NULL); apr_hook_global_pool = pool; server = apr_palloc(pool, sizeof(server_rec)); server->addrs = apr_palloc(pool, sizeof(server_addr_rec)); server->addrs->host_addr = apr_palloc(pool, sizeof(apr_sockaddr_t)); server->addrs->host_addr->addr_str_len = 16; server->addrs->host_addr->family = AF_INET; server->addrs->host_addr->hostname = sa_name; #ifdef WIN32 server->addrs->host_addr->ipaddr_len = sizeof(IN_ADDR); #else server->addrs->host_addr->ipaddr_len = sizeof(struct in_addr); #endif server->addrs->host_addr->ipaddr_ptr = &server->addrs->host_addr->sa.sin.sin_addr; server->addrs->host_addr->pool = pool; server->addrs->host_addr->port = 80; #ifdef WIN32 server->addrs->host_addr->sa.sin.sin_addr.S_un.S_addr = 0x0100007f; #else server->addrs->host_addr->sa.sin.sin_addr.s_addr = 0x0100007f; #endif server->addrs->host_addr->sa.sin.sin_family = AF_INET; server->addrs->host_addr->sa.sin.sin_port = 80; server->addrs->host_addr->salen = sizeof(server->addrs->host_addr->sa); server->addrs->host_addr->servname = sa_name; server->addrs->host_port = 80; server->error_fname = "error.log"; server->error_log = NULL; server->limit_req_fields = 1024; server->limit_req_fieldsize = 1024; server->limit_req_line = 1024; #if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER < 3 server->loglevel = APLOG_DEBUG; #endif server->lookup_defaults = NULL; server->module_config = NULL; server->names = NULL; #ifdef WIN32 server->path = "c:\\inetpub\\wwwroot"; #else server->path = "/var/www"; #endif server->pathlen = strlen(server->path); server->port = 80; server->process = apr_palloc(pool, sizeof(process_rec)); server->process->argc = 1; server->process->argv = sa_name_argv; server->process->pconf = pool; server->process->pool = pool; server->process->short_name = sa_name; server->server_admin = sa_name; server->server_hostname = sa_name; server->server_scheme = ""; server->timeout = 60 * 1000000;// 60 seconds server->wild_names = NULL; server->is_virtual = 0; ap_server_config_defines = apr_array_make(pool, 1, sizeof(char *)); // here we should add scoreboard handling for multiple processes and threads // ap_scoreboard_image = (scoreboard *)apr_palloc(pool, sizeof(scoreboard)); memset(ap_scoreboard_image, 0, sizeof(scoreboard)); // ---------- security2_module.module_index = 0; security2_module.register_hooks(pool); ap_register_input_filter("HTTP_IN", ap_http_in_filter, NULL, AP_FTYPE_RESOURCE); ap_register_output_filter("HTTP_OUT", ap_http_out_filter, NULL, AP_FTYPE_CONTENT_SET); return server; } apr_status_t ap_http_in_filter(ap_filter_t *f, apr_bucket_brigade *bb_out, ap_input_mode_t mode, apr_read_type_e block, apr_off_t readbytes) { char *tmp = NULL; apr_bucket *e = NULL; unsigned int readcnt = 0; int is_eos = 0; apr_bucket_brigade *bb_in; apr_bucket *after; apr_status_t rv; bb_in = modsecGetBodyBrigade(f->r); /* use request brigade */ if (bb_in != NULL) { if (!APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb_in))) { e = apr_bucket_eos_create(f->c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bb_in, e); } rv = apr_brigade_partition(bb_in, readbytes, &after); if (rv != APR_SUCCESS && rv != APR_INCOMPLETE) { return rv; } for (e = APR_BRIGADE_FIRST(bb_in); e != after; e = APR_BRIGADE_FIRST(bb_in)) { APR_BUCKET_REMOVE(e); APR_BRIGADE_INSERT_TAIL(bb_out, e); } return APR_SUCCESS; } /* call the callback */ if(modsecReadBody != NULL) { tmp = (char *)apr_palloc(f->r->pool, readbytes); modsecReadBody(f->r, tmp, readbytes, &readcnt, &is_eos); e = apr_bucket_pool_create(tmp, readcnt, f->r->pool, f->c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bb_out, e); if(is_eos) { e = apr_bucket_eos_create(f->c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bb_out, e); } return APR_SUCCESS; } /* cannot read request body */ e = apr_bucket_eos_create(f->c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bb_out, e); return APR_SUCCESS; } apr_status_t ap_http_out_filter(ap_filter_t *f, apr_bucket_brigade *b) { apr_bucket_brigade *bb_out = (apr_bucket_brigade *)f->ctx; APR_BRIGADE_CONCAT(bb_out, b); return APR_SUCCESS; } void modsecTerminate() { apr_pool_destroy(pool); pool = NULL; apr_terminate(); } void modsecStartConfig() { apr_pool_t *ptemp = NULL; apr_pool_create(&ptemp, pool); hookfn_pre_config(pool, pool, ptemp); apr_pool_destroy(ptemp); } directory_config *modsecGetDefaultConfig() { return (directory_config *)security2_module.create_dir_config(pool, NULL); } const char *modsecProcessConfig(directory_config *config, const char *file, const char *dir) { apr_pool_t *ptemp = NULL; const char *err; apr_status_t status; const char *rootpath, *incpath; if(dir == NULL || strlen(dir) == 0) #ifdef WIN32 dir = "\\"; #else dir = "/"; #endif incpath = file; /* locate the start of the directories proper */ status = apr_filepath_root(&rootpath, &incpath, APR_FILEPATH_TRUENAME | APR_FILEPATH_NATIVE, config->mp); /* we allow APR_SUCCESS and APR_EINCOMPLETE */ if (APR_ERELATIVE == status) { int li = strlen(dir) - 1; if(dir[li] != '/' && dir[li] != '\\') #ifdef WIN32 file = apr_pstrcat(config->mp, dir, "\\", file, NULL); #else file = apr_pstrcat(config->mp, dir, "/", file, NULL); #endif else file = apr_pstrcat(config->mp, dir, file, NULL); } else if (APR_EBADPATH == status) { return apr_pstrcat(config->mp, "Config file has a bad path, ", file, NULL); } apr_pool_create(&ptemp, config->mp); err = process_command_config(server, config, config->mp, ptemp, file); apr_pool_destroy(ptemp); return err; } void modsecFinalizeConfig() { apr_pool_t *ptemp = NULL; apr_pool_create(&ptemp, pool); hookfn_post_config(pool, pool, ptemp, server); hookfn_post_config(pool, pool, ptemp, server); apr_pool_destroy(ptemp); } void modsecInitProcess() { hookfn_child_init(pool, server); } conn_rec *modsecNewConnection() { conn_rec *c; apr_pool_t *pc = NULL; apr_pool_create(&pc, pool); c = apr_pcalloc(pc, sizeof(conn_rec)); c->base_server = server; c->id = 1; c->local_addr = server->addrs->host_addr; c->local_host = sa_name; c->local_ip = "127.0.0.1"; c->pool = pc; c->remote_host = sa_name; #if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER < 3 c->remote_ip = "127.0.0.1"; c->remote_addr = server->addrs->host_addr; #else c->client_ip = "127.0.0.1"; c->client_addr = server->addrs->host_addr; #endif c->input_filters = NULL; c->output_filters = NULL; c->bucket_alloc = apr_bucket_alloc_create(pc); return c; } void modsecProcessConnection(conn_rec *c) { hookfn_process_connection(c); } request_rec *modsecNewRequest(conn_rec *connection, directory_config *config) { request_rec *r; apr_pool_t *pr = NULL; apr_pool_create(&pr, connection->pool); r = apr_pcalloc(pr, sizeof(request_rec)); r->connection = connection; r->server = server; r->pool = pr; r->main = NULL; r->next = NULL; r->notes = apr_table_make(pr, 10); r->per_dir_config = apr_palloc(pr, sizeof(void *)); ((void **)r->per_dir_config)[0] = config; r->prev = NULL; r->subprocess_env = apr_table_make(pr, 10); apr_table_setn(r->subprocess_env, "UNIQUE_ID", "unique_id"); r->user = NULL; r->headers_in = apr_table_make(pr, 10); r->headers_out = apr_table_make(pr, 10); r->err_headers_out = apr_table_make(pr, 10); //apr_table_setn(r->headers_in, "Host", "www.google.com"); //apr_table_setn(r->headers_in, "", ""); r->the_request = "GET /../../index.html HTTP/1.1"; r->method = "GET"; r->method_number = M_GET; r->protocol = "HTTP/1.1"; r->uri = "http://www.google.com/../../index.html"; r->args = ""; r->filename = "/../../index.html"; r->handler = "IIS"; r->parsed_uri.scheme = "http"; r->parsed_uri.path = "/../../index.html"; r->parsed_uri.hostname = "www.google.com"; r->parsed_uri.is_initialized = 1; r->parsed_uri.port = 1234; r->parsed_uri.port_str = "1234"; r->parsed_uri.query = ""; r->parsed_uri.dns_looked_up = 0; r->parsed_uri.dns_resolved = 0; r->parsed_uri.password = NULL; r->parsed_uri.user = NULL; r->parsed_uri.fragment = ""; r->input_filters = NULL; r->output_filters = NULL; return r; } static modsec_rec *retrieve_msr(request_rec *r) { modsec_rec *msr = NULL; request_rec *rx = NULL; /* Look in the current request first. */ msr = (modsec_rec *)apr_table_get(r->notes, NOTE_MSR); if (msr != NULL) { msr->r = r; return msr; } /* If this is a subrequest then look in the main request. */ if (r->main != NULL) { msr = (modsec_rec *)apr_table_get(r->main->notes, NOTE_MSR); if (msr != NULL) { msr->r = r; return msr; } } /* If the request was redirected then look in the previous requests. */ rx = r->prev; while(rx != NULL) { msr = (modsec_rec *)apr_table_get(rx->notes, NOTE_MSR); if (msr != NULL) { msr->r = r; return msr; } rx = rx->prev; } return NULL; } int modsecProcessRequestHeaders(request_rec *r) { return hookfn_post_read_request(r); } int modsecProcessRequestBody(request_rec *r) { int status = DECLINED; modsec_rec *msr = NULL; ap_filter_t *f = ap_add_input_filter("HTTP_IN", NULL, r, r->connection); apr_bucket_brigade* bb_out; status = hookfn_fixups(r); ap_remove_input_filter(f); if (status != DECLINED) { return status; } hookfn_insert_filter(r); /* Find the transaction context first. */ msr = retrieve_msr(r); if (msr == NULL) return status; bb_out = modsecGetBodyBrigade(r); if (bb_out) { (void) apr_brigade_cleanup(bb_out); status = ap_get_brigade(r->input_filters, bb_out, AP_MODE_READBYTES, APR_BLOCK_READ, -1); if (status == APR_SUCCESS) { return DECLINED; } return status; } if(msr->stream_input_data != NULL && modsecWriteBody != NULL) { // target is responsible for copying the data into correctly managed buffer // modsecWriteBody(r, msr->stream_input_data, msr->stream_input_length); free(msr->stream_input_data); msr->stream_input_data = NULL; } // leftover code possibly for future use // //if(r->input_filters != NULL && r->input_filters->frec->filter_init_func != NULL) //r->input_filters->frec->filter_init_func(r->input_filters); //if(r->input_filters != NULL && r->input_filters->frec->filter_func.in_func != NULL) //r->input_filters->frec->filter_func.in_func(r->input_filters, NULL, 0, 0, 0); return status; } void modsecSetConfigForIISRequestBody(request_rec *r) { modsec_rec *msr = retrieve_msr(r); if(msr == NULL || msr->txcfg == NULL) return; if(msr->txcfg->reqbody_access) msr->txcfg->stream_inbody_inspection = 1; } int modsecContextState(request_rec *r) { modsec_rec *msr = retrieve_msr(r); if(msr == NULL || msr->txcfg == NULL) return NOT_SET; return msr->txcfg->is_enabled; } int modsecIsRequestBodyAccessEnabled(request_rec *r) { modsec_rec *msr = retrieve_msr(r); if(msr == NULL || msr->txcfg == NULL) return 0; return msr->txcfg->reqbody_access; } int modsecIsResponseBodyAccessEnabled(request_rec *r) { modsec_rec *msr = retrieve_msr(r); if(msr == NULL || msr->txcfg == NULL) return 0; return msr->txcfg->resbody_access; } int modsecProcessResponse(request_rec *r) { int status; modsec_rec *msr; apr_bucket *e; ap_filter_t *f; apr_bucket_brigade *bb_in, *bb_out, *bb; if(r->output_filters == NULL) { return DECLINED; } msr = (modsec_rec *)r->output_filters->ctx; if (msr == NULL) { ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r->server, "ModSecurity: Internal Error: msr is null in output filter."); ap_remove_output_filter(r->output_filters); return APR_EGENERAL; } msr->r = r; /* create input response brigade */ bb_in = apr_brigade_create(msr->mp, r->connection->bucket_alloc); if (bb_in == NULL) { msr_log(msr, 1, "Process response: Failed to create brigade."); return APR_EGENERAL; } /* get input response brigade */ bb = modsecGetResponseBrigade(r); if (bb != NULL) { APR_BRIGADE_CONCAT(bb_in, bb); if (!APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb_in))) { e = apr_bucket_eos_create(bb_in->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bb_in, e); } } else if (modsecReadResponse != NULL) { unsigned int readcnt = 0; int is_eos = 0; char buf[8192]; while(!is_eos) { modsecReadResponse(r, buf, 8192, &readcnt, &is_eos); if(readcnt > 0) { char *tmp = (char *)apr_palloc(r->pool, readcnt); memcpy(tmp, buf, readcnt); e = apr_bucket_pool_create(tmp, readcnt, r->pool, r->connection->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bb_in, e); } } e = apr_bucket_eos_create(r->connection->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bb_in, e); } else { /* cannot read response body process header only */ e = apr_bucket_eos_create(r->connection->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bb_in, e); } bb_out = bb ? bb : apr_brigade_create(msr->mp, r->connection->bucket_alloc); if (bb_out == NULL) { msr_log(msr, 1, "Process response: Failed to create brigade."); return APR_EGENERAL; } /* concat output bucket to bb_out */ f = ap_add_output_filter("HTTP_OUT", bb_out, r, r->connection); status = ap_pass_brigade(r->output_filters, bb_in); ap_remove_output_filter(f); if (status == APR_EGENERAL) { /* retrive response status from bb_out */ for(e = APR_BRIGADE_FIRST(bb_out); e != APR_BRIGADE_SENTINEL(bb_out); e = APR_BUCKET_NEXT(e)) { if (AP_BUCKET_IS_ERROR(e)) { return ((ap_bucket_error*) e->data)->status; } } return APR_EGENERAL; } if (status != DECLINED) { return status; } /* copy bb_out */ // is there a way to tell whether the response body was modified or not? if (modsecWriteResponse != NULL && (msr->txcfg->content_injection_enabled || msr->content_prepend_len != 0 || msr->content_append_len != 0) && msr->txcfg->resbody_access) { char *data = NULL; apr_size_t length; status = apr_brigade_pflatten(msr->of_brigade, &data, &length, msr->mp); if (status != APR_SUCCESS) { msr_log(msr, 1, "Output filter: Failed to flatten brigade (%d): %s", status, get_apr_error(msr->mp, status)); return APR_EGENERAL; } if ( modsecWriteResponse(msr->r, data, msr->stream_output_length) != APR_SUCCESS) { return APR_EGENERAL; } } return DECLINED; } int modsecFinishRequest(request_rec *r) { // run output filter //if(r->output_filters != NULL && r->output_filters->frec->filter_init_func != NULL) //r->output_filters->frec->filter_init_func(r->output_filters); hookfn_log_transaction(r); // make sure you cleanup before calling apr_terminate() // otherwise double-free might occur, because of the request body pool cleanup function // apr_pool_destroy(r->pool); return DECLINED; } // destroy only the connection pool int modsecFinishConnection(conn_rec *c) { apr_pool_destroy(c->pool); return 0; } void modsecSetLogHook(void *obj, void (*hook)(void *obj, int level, char *str)) { modsecLogObj = obj; modsecLogHook = hook; } void modsecSetReadBody(apr_status_t (*func)(request_rec *r, char *buf, unsigned int length, unsigned int *readcnt, int *is_eos)) { modsecReadBody = func; } void modsecSetReadResponse(apr_status_t (*func)(request_rec *r, char *buf, unsigned int length, unsigned int *readcnt, int *is_eos)) { modsecReadResponse = func; } void modsecSetWriteBody(apr_status_t (*func)(request_rec *r, char *buf, unsigned int length)) { modsecWriteBody = func; } void modsecSetWriteResponse(apr_status_t (*func)(request_rec *r, char *buf, unsigned int length)) { modsecWriteResponse = func; } void modsecSetDropAction(int (*func)(request_rec *r)) { modsecDropAction = func; } /* * Case SecServerSignature was used, this function returns the banner that * should be used, otherwise it returns NULL. */ const char *modsecIsServerSignatureAvailale(void) { return new_server_signature; } #ifdef VERSION_IIS void modsecStatusEngineCall() { if (status_engine_state != STATUS_ENGINE_DISABLED) { msc_status_engine_call(); } else { ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, NULL, "Status engine is currently disabled, enable it by set " \ "SecStatusEngine to On.\n"); } } void modsecReportRemoteLoadedRules() { #ifdef WITH_REMOTE_RULES if (remote_rules_server != NULL) { if (remote_rules_server->amount_of_rules == 1) { ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, NULL, "ModSecurity: Loaded %d rule from: '%s'.", remote_rules_server->amount_of_rules, remote_rules_server->uri); } else { ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, NULL, "ModSecurity: Loaded %d rules from: '%s'.", remote_rules_server->amount_of_rules, remote_rules_server->uri); } } #endif if (remote_rules_fail_message != NULL) { ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, NULL, "ModSecurity: " \ "Problems loading external resources: %s", remote_rules_fail_message); } } #endif