2
* ModSecurity for Apache 2.x, http://www.modsecurity.org/
3
* Copyright (c) 2004-2008 Breach Security, Inc. (http://www.breach.com/)
5
* This product is released under the terms of the General Public Licence,
6
* version 2 (GPLv2). Please refer to the file LICENSE (included with this
7
* distribution) which contains the complete text of the licence.
9
* There are special exceptions to the terms and conditions of the GPL
10
* as it is applied to this software. View the full text of the exception in
11
* file MODSECURITY_LICENSING_EXCEPTION in the directory of this software
14
* If any of the files related to licensing are missing or if you have any
15
* other questions related to licensing please contact Breach Security, Inc.
16
* directly using the email address support@breach.com.
21
#include "apr_global_mutex.h"
23
#include "modsecurity.h"
24
#include "msc_parsers.h"
28
modsec_build_type_rec DSOLOCAL modsec_build_type[] = {
29
{ "-dev", 1 }, /* Development build */
30
{ "-rc", 3 }, /* Release Candidate build */
31
{ "", 9 }, /* Production build */
32
{ "-breach", 9 }, /* Breach build */
33
{ "-trunk", 9 }, /* Trunk build */
34
{ NULL, -1 } /* terminator */
38
* Log an alert message to the log, adding the rule metadata at the end.
40
void msc_alert(modsec_rec *msr, int level, msre_actionset *actionset, const char *action_message,
41
const char *rule_message)
43
const char *message = NULL;
45
if (rule_message == NULL) rule_message = "Unknown error.";
47
message = apr_psprintf(msr->mp, "%s %s%s", action_message,
48
rule_message, msre_format_metadata(msr, actionset));
50
msr_log(msr, level, "%s", message);
55
* Return phase name associated with the given phase number.
57
static const char *phase_name(int phase) {
60
return "REQUEST_HEADERS";
63
return "REQUEST_BODY";
66
return "RESPONSE_HEADERS";
69
return "RESPONSE_BODY";
80
* Creates and initialises a ModSecurity engine instance.
82
msc_engine *modsecurity_create(apr_pool_t *mp, int processing_mode) {
83
msc_engine *msce = NULL;
85
msce = apr_pcalloc(mp, sizeof(msc_engine));
86
if (msce == NULL) return NULL;
89
msce->processing_mode = processing_mode;
91
msce->msre = msre_engine_create(msce->mp);
92
if (msce->msre == NULL) return NULL;
93
msre_engine_register_default_variables(msce->msre);
94
msre_engine_register_default_operators(msce->msre);
95
msre_engine_register_default_tfns(msce->msre);
96
msre_engine_register_default_actions(msce->msre);
102
* Initialise the modsecurity engine. This function must be invoked
103
* after configuration processing is complete as Apache needs to know the
104
* username it is running as.
106
int modsecurity_init(msc_engine *msce, apr_pool_t *mp) {
109
/* Serial audit log mutext */
110
rc = apr_global_mutex_create(&msce->auditlog_lock, NULL, APR_LOCK_DEFAULT, mp);
111
if (rc != APR_SUCCESS) {
112
//ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, "mod_security: Could not create modsec_auditlog_lock");
113
//return HTTP_INTERNAL_SERVER_ERROR;
117
#ifdef __SET_MUTEX_PERMS
118
rc = unixd_set_global_mutex_perms(msce->auditlog_lock);
119
if (rc != APR_SUCCESS) {
120
// ap_log_error(APLOG_MARK, APLOG_ERR, rc, s, "mod_security: Could not set permissions on modsec_auditlog_lock; check User and Group directives");
121
// return HTTP_INTERNAL_SERVER_ERROR;
130
* Performs per-child (new process) initialisation.
132
void modsecurity_child_init(msc_engine *msce) {
133
/* Need to call this once per process before any other XML calls. */
136
if (msce->auditlog_lock != NULL) {
137
apr_status_t rc = apr_global_mutex_child_init(&msce->auditlog_lock, NULL, msce->mp);
138
if (rc != APR_SUCCESS) {
139
// ap_log_error(APLOG_MARK, APLOG_ERR, rs, s, "Failed to child-init auditlog mutex");
145
* Releases resources held by engine instance.
147
void modsecurity_shutdown(msc_engine *msce) {
148
if (msce == NULL) return;
154
static apr_status_t modsecurity_tx_cleanup(void *data) {
155
modsec_rec *msr = (modsec_rec *)data;
156
const apr_array_header_t *arr;
157
apr_table_entry_t *te;
158
int collect_garbage = 0;
160
char *my_error_msg = NULL;
162
if (msr == NULL) return APR_SUCCESS;
164
if (rand() < RAND_MAX/100) {
168
/* Collections, store & remove stale. */
169
arr = apr_table_elts(msr->collections);
170
te = (apr_table_entry_t *)arr->elts;
171
for (i = 0; i < arr->nelts; i++) {
172
apr_table_t *col = (apr_table_t *)te[i].val;
174
/* Only store those collections that changed. */
175
if (apr_table_get(msr->collections_dirty, te[i].key)) {
176
collection_store(msr, col);
179
if (collect_garbage) {
180
collections_remove_stale(msr, te[i].key);
184
/* Multipart processor cleanup. */
185
if (msr->mpd != NULL) multipart_cleanup(msr);
187
/* XML processor cleanup. */
188
if (msr->xml != NULL) xml_cleanup(msr);
190
// TODO: Why do we ignore return code here?
191
modsecurity_request_body_clear(msr, &my_error_msg);
192
if (my_error_msg != NULL) {
193
msr_log(msr, 1, "%s", my_error_msg);
202
apr_status_t modsecurity_tx_init(modsec_rec *msr) {
203
const char *s = NULL;
204
const apr_array_header_t *arr;
205
apr_table_entry_t *te;
208
/* Register TX cleanup */
209
apr_pool_cleanup_register(msr->mp, msr, modsecurity_tx_cleanup, apr_pool_cleanup_null);
212
msr->request_content_length = -1;
213
s = apr_table_get(msr->request_headers, "Content-Length");
215
msr->request_content_length = strtol(s, NULL, 10);
218
/* Figure out whether this request has a body */
219
msr->reqbody_chunked = 0;
220
msr->reqbody_should_exist = 0;
221
if (msr->request_content_length == -1) {
222
/* There's no C-L, but is chunked encoding used? */
223
char *transfer_encoding = (char *)apr_table_get(msr->request_headers, "Transfer-Encoding");
224
if ((transfer_encoding != NULL)&&(strstr(transfer_encoding, "chunked") != NULL)) {
225
msr->reqbody_should_exist = 1;
226
msr->reqbody_chunked = 1;
230
msr->reqbody_should_exist = 1;
234
msr->request_content_type = NULL;
235
s = apr_table_get(msr->request_headers, "Content-Type");
236
if (s != NULL) msr->request_content_type = s;
238
/* Decide what to do with the request body. */
239
if ((msr->request_content_type != NULL)
240
&& (strncasecmp(msr->request_content_type, "application/x-www-form-urlencoded", 33) == 0))
242
/* Always place POST requests with
243
* "application/x-www-form-urlencoded" payloads in memory.
245
msr->msc_reqbody_storage = MSC_REQBODY_MEMORY;
246
msr->msc_reqbody_spilltodisk = 0;
247
msr->msc_reqbody_processor = "URLENCODED";
249
/* If the C-L is known and there's more data than
250
* our limit go to disk straight away.
252
if ((msr->request_content_length != -1)
253
&& (msr->request_content_length > msr->txcfg->reqbody_inmemory_limit))
255
msr->msc_reqbody_storage = MSC_REQBODY_DISK;
258
/* In all other cases, try using the memory first
259
* but switch over to disk for larger bodies.
261
msr->msc_reqbody_storage = MSC_REQBODY_MEMORY;
262
msr->msc_reqbody_spilltodisk = 1;
264
if (msr->request_content_type != NULL) {
265
if (strncasecmp(msr->request_content_type, "multipart/form-data", 19) == 0) {
266
msr->msc_reqbody_processor = "MULTIPART";
271
/* Initialise arguments */
272
msr->arguments = apr_table_make(msr->mp, 32);
273
if (msr->arguments == NULL) return -1;
274
if (msr->query_string != NULL) {
275
int invalid_count = 0;
277
if (parse_arguments(msr, msr->query_string, strlen(msr->query_string),
278
msr->txcfg->argument_separator, "QUERY_STRING", msr->arguments,
281
msr_log(msr, 1, "Initialisation: Error occurred while parsing QUERY_STRING arguments.");
286
msr->arguments_to_sanitise = apr_table_make(msr->mp, 16);
287
if (msr->arguments_to_sanitise == NULL) return -1;
288
msr->request_headers_to_sanitise = apr_table_make(msr->mp, 16);
289
if (msr->request_headers_to_sanitise == NULL) return -1;
290
msr->response_headers_to_sanitise = apr_table_make(msr->mp, 16);
291
if (msr->response_headers_to_sanitise == NULL) return -1;
293
/* Initialise cookies */
294
msr->request_cookies = apr_table_make(msr->mp, 16);
295
if (msr->request_cookies == NULL) return -1;
297
/* Locate the cookie headers and parse them */
298
arr = apr_table_elts(msr->request_headers);
299
te = (apr_table_entry_t *)arr->elts;
300
for (i = 0; i < arr->nelts; i++) {
301
if (strcasecmp(te[i].key, "Cookie") == 0) {
302
if (msr->txcfg->cookie_format == COOKIES_V0) {
303
parse_cookies_v0(msr, te[i].val, msr->request_cookies);
305
parse_cookies_v1(msr, te[i].val, msr->request_cookies);
311
msr->tx_vars = apr_table_make(msr->mp, 32);
312
if (msr->tx_vars == NULL) return -1;
314
msr->geo_vars = apr_table_make(msr->mp, 8);
315
if (msr->geo_vars == NULL) return -1;
317
msr->collections = apr_table_make(msr->mp, 8);
318
if (msr->collections == NULL) return -1;
319
msr->collections_dirty = apr_table_make(msr->mp, 8);
320
if (msr->collections_dirty == NULL) return -1;
324
msr->tcache_items = 0;
326
msr->matched_rules = apr_array_make(msr->mp, 16, sizeof(void *));
327
if (msr->matched_rules == NULL) return -1;
329
msr->matched_var = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
330
if (msr->matched_var == NULL) return -1;
332
msr->highest_severity = 255; /* high, invalid value */
334
msr->removed_rules = apr_array_make(msr->mp, 16, sizeof(char *));
335
if (msr->removed_rules == NULL) return -1;
343
static int is_response_status_relevant(modsec_rec *msr, int status) {
344
char *my_error_msg = NULL;
348
/* ENH: Setting is_relevant here will cause an audit even if noauditlog
349
* was set for the last rule that matched. Is this what we want?
352
if ((msr->txcfg->auditlog_relevant_regex == NULL)
353
||(msr->txcfg->auditlog_relevant_regex == NOT_SET_P))
358
apr_snprintf(buf, sizeof(buf), "%d", status);
360
rc = msc_regexec(msr->txcfg->auditlog_relevant_regex, buf, strlen(buf), &my_error_msg);
361
if (rc >= 0) return 1;
362
if (rc == PCRE_ERROR_NOMATCH) return 0;
364
msr_log(msr, 1, "Regex processing failed (rc %d): %s", rc, my_error_msg);
371
static apr_status_t modsecurity_process_phase_request_headers(modsec_rec *msr) {
372
msr_log(msr, 4, "Starting phase REQUEST_HEADERS.");
374
if (msr->txcfg->ruleset != NULL) {
375
return msre_ruleset_process_phase(msr->txcfg->ruleset, msr);
384
static apr_status_t modsecurity_process_phase_request_body(modsec_rec *msr) {
385
if ((msr->allow_scope == ACTION_ALLOW_REQUEST)||(msr->allow_scope == ACTION_ALLOW)) {
386
msr_log(msr, 4, "Skipping phase REQUEST_BODY (allow used).");
389
msr_log(msr, 4, "Starting phase REQUEST_BODY.");
392
if (msr->txcfg->ruleset != NULL) {
393
return msre_ruleset_process_phase(msr->txcfg->ruleset, msr);
402
static apr_status_t modsecurity_process_phase_response_headers(modsec_rec *msr) {
403
if (msr->allow_scope == ACTION_ALLOW) {
404
msr_log(msr, 4, "Skipping phase RESPONSE_HEADERS (allow used).");
407
msr_log(msr, 4, "Starting phase RESPONSE_HEADERS.");
410
if (msr->txcfg->ruleset != NULL) {
411
return msre_ruleset_process_phase(msr->txcfg->ruleset, msr);
420
static apr_status_t modsecurity_process_phase_response_body(modsec_rec *msr) {
421
if (msr->allow_scope == ACTION_ALLOW) {
422
msr_log(msr, 4, "Skipping phase RESPONSE_BODY (allow used).");
425
msr_log(msr, 4, "Starting phase RESPONSE_BODY.");
428
if (msr->txcfg->ruleset != NULL) {
429
return msre_ruleset_process_phase(msr->txcfg->ruleset, msr);
438
static apr_status_t modsecurity_process_phase_logging(modsec_rec *msr) {
439
msr_log(msr, 4, "Starting phase LOGGING.");
441
if (msr->txcfg->ruleset != NULL) {
442
msre_ruleset_process_phase(msr->txcfg->ruleset, msr);
445
/* Is this request relevant for logging purposes? */
446
if (msr->is_relevant == 0) {
447
/* Check the status */
448
msr->is_relevant += is_response_status_relevant(msr, msr->r->status);
450
/* If we processed two requests and statuses are different then
451
* check the other status too.
453
if (msr->r_early->status != msr->r->status) {
454
msr->is_relevant += is_response_status_relevant(msr, msr->r_early->status);
458
/* Figure out if we want to keep the files (if there are any, of course). */
459
if ((msr->txcfg->upload_keep_files == KEEP_FILES_ON)
460
|| ((msr->txcfg->upload_keep_files == KEEP_FILES_RELEVANT_ONLY)&&(msr->is_relevant)))
462
msr->upload_remove_files = 0;
464
msr->upload_remove_files = 1;
467
/* Are we configured for audit logging? */
468
switch(msr->txcfg->auditlog_flag) {
470
msr_log(msr, 4, "Audit log: Not configured to run for this request.");
474
case AUDITLOG_RELEVANT :
475
if (msr->is_relevant == 0) {
476
msr_log(msr, 4, "Audit log: Ignoring a non-relevant request.");
482
/* All right, do nothing */
486
msr_log(msr, 1, "Internal error: Could not determine if auditing is needed, so forcing auditing.");
490
/* Invoke the Audit logger */
491
msr_log(msr, 4, "Audit log: Logging this transaction.");
493
sec_audit_logger(msr);
499
* Processes one transaction phase. The phase number does not
500
* need to be explicitly provided since it's already available
501
* in the modsec_rec structure.
503
apr_status_t modsecurity_process_phase(modsec_rec *msr, unsigned int phase) {
504
/* Check if we should run. */
505
if ((msr->was_intercepted)&&(phase != PHASE_LOGGING)) {
506
msr_log(msr, 4, "Skipping phase %d as request was already intercepted.", phase);
510
/* Do not process the same phase twice. */
511
if (msr->phase >= phase) {
512
msr_log(msr, 4, "Skipping phase %d because it was previously run (at %d now).",
519
/* Clear out the transformation cache at the start of each phase */
520
if (msr->txcfg->cache_trans == MODSEC_CACHE_ENABLED) {
522
apr_hash_index_t *hi;
528
apr_pool_t *mp = msr->msc_rule_mptmp;
529
const apr_array_header_t *ctarr;
530
const apr_table_entry_t *ctelts;
535
apr_pool_t *mp = msr->mp;
538
for (hi = apr_hash_first(mp, msr->tcache); hi; hi = apr_hash_next(hi)) {
539
apr_hash_this(hi, &key, &klen, &dummy);
540
tab = (apr_table_t *)dummy;
542
if (tab == NULL) continue;
545
/* Dump the cache out as we clear */
546
ctarr = apr_table_elts(tab);
547
ctelts = (const apr_table_entry_t*)ctarr->elts;
548
for (ri = 0; ri < ctarr->nelts; ri++) {
550
rec = (msre_cache_rec *)ctelts[ri].val;
552
if (msr->txcfg->debuglog_level >= 9) {
553
msr_log(msr, 9, "CACHE: %5d) hits=%d key=%pp %x;%s=\"%s\" (%pp - %pp)", cn, rec->hits, key, rec->num, rec->path, log_escape_nq_ex(mp, rec->val, rec->val_len), rec->val, rec->val + rec->val_len);
557
if (msr->txcfg->debuglog_level >= 9) {
558
msr_log(msr, 9, "CACHE: %5d) hits=%d key=%pp %x;%s=<no change>", cn, rec->hits, key, rec->num, rec->path);
564
apr_table_clear(tab);
565
apr_hash_set(msr->tcache, key, klen, NULL);
568
msr_log(msr, 9, "Cleared transformation cache for phase %d", msr->phase);
571
msr->tcache_items = 0;
572
msr->tcache = apr_hash_make(msr->mp);
573
if (msr->tcache == NULL) return -1;
578
return modsecurity_process_phase_request_headers(msr);
581
return modsecurity_process_phase_request_body(msr);
584
return modsecurity_process_phase_response_headers(msr);
587
return modsecurity_process_phase_response_body(msr);
590
return modsecurity_process_phase_logging(msr);
593
msr_log(msr, 1, "Invalid processing phase: %d", msr->phase);