Imported Upstream version 2.5.11
[libapache-mod-security.git] / apache2 / modsecurity.c
1 /*
2  * ModSecurity for Apache 2.x, http://www.modsecurity.org/
3  * Copyright (c) 2004-2009 Breach Security, Inc. (http://www.breach.com/)
4  *
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.
8  *
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
12  * distribution.
13  *
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.
17  *
18  */
19 #include <stdlib.h>
20
21 #include "apr_global_mutex.h"
22
23 #include "modsecurity.h"
24 #include "msc_parsers.h"
25 #include "msc_util.h"
26 #include "msc_xml.h"
27
28 /**
29  * Format an alert message.
30  */
31 const char * msc_alert_message(modsec_rec *msr, msre_actionset *actionset, const char *action_message,
32     const char *rule_message)
33 {
34     const char *message = NULL;
35
36     if (rule_message == NULL) rule_message = "Unknown error.";
37
38     if (action_message == NULL) {
39         message = apr_psprintf(msr->mp, "%s%s",
40             rule_message, msre_format_metadata(msr, actionset));
41     }
42     else {
43         message = apr_psprintf(msr->mp, "%s %s%s", action_message,
44             rule_message, msre_format_metadata(msr, actionset));
45     }
46
47     return message;
48 }
49
50 /**
51  * Log an alert message to the log, adding the rule metadata at the end.
52  */
53 void msc_alert(modsec_rec *msr, int level, msre_actionset *actionset, const char *action_message,
54     const char *rule_message)
55 {
56     const char *message = msc_alert_message(msr, actionset, action_message, rule_message);
57
58     msr_log(msr, level, "%s", message);
59 }
60
61 #if 0
62 /**
63  * Return phase name associated with the given phase number.
64  */
65 static const char *phase_name(int phase) {
66     switch(phase) {
67         case 1 :
68             return "REQUEST_HEADERS";
69             break;
70         case 2 :
71             return "REQUEST_BODY";
72             break;
73         case 3 :
74             return "RESPONSE_HEADERS";
75             break;
76         case 4 :
77             return "RESPONSE_BODY";
78             break;
79         case 5 :
80             return "LOGGING";
81             break;
82     }
83     return "INVALID";
84 }
85 #endif
86
87 /**
88  * Creates and initialises a ModSecurity engine instance.
89  */
90 msc_engine *modsecurity_create(apr_pool_t *mp, int processing_mode) {
91     msc_engine *msce = NULL;
92
93     msce = apr_pcalloc(mp, sizeof(msc_engine));
94     if (msce == NULL) return NULL;
95
96     msce->mp = mp;
97     msce->processing_mode = processing_mode;
98
99     msce->msre = msre_engine_create(msce->mp);
100     if (msce->msre == NULL) return NULL;
101     msre_engine_register_default_variables(msce->msre);
102     msre_engine_register_default_operators(msce->msre);
103     msre_engine_register_default_tfns(msce->msre);
104     msre_engine_register_default_actions(msce->msre);
105
106     return msce;
107 }
108
109 /**
110  * Initialise the modsecurity engine. This function must be invoked
111  * after configuration processing is complete as Apache needs to know the
112  * username it is running as.
113  */
114 int modsecurity_init(msc_engine *msce, apr_pool_t *mp) {
115     apr_status_t rc;
116
117     /* Serial audit log mutext */
118     rc = apr_global_mutex_create(&msce->auditlog_lock, NULL, APR_LOCK_DEFAULT, mp);
119     if (rc != APR_SUCCESS) {
120         //ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, "mod_security: Could not create modsec_auditlog_lock");
121         //return HTTP_INTERNAL_SERVER_ERROR;
122         return -1;
123     }
124
125     #ifdef __SET_MUTEX_PERMS
126     rc = unixd_set_global_mutex_perms(msce->auditlog_lock);
127     if (rc != APR_SUCCESS) {
128         // ap_log_error(APLOG_MARK, APLOG_ERR, rc, s, "mod_security: Could not set permissions on modsec_auditlog_lock; check User and Group directives");
129         // return HTTP_INTERNAL_SERVER_ERROR;
130         return -1;
131     }
132     #endif
133
134     return 1;
135 }
136
137 /**
138  * Performs per-child (new process) initialisation.
139  */
140 void modsecurity_child_init(msc_engine *msce) {
141     /* Need to call this once per process before any other XML calls. */
142     xmlInitParser();
143
144     if (msce->auditlog_lock != NULL) {
145         apr_status_t rc = apr_global_mutex_child_init(&msce->auditlog_lock, NULL, msce->mp);
146         if (rc != APR_SUCCESS) {
147             // ap_log_error(APLOG_MARK, APLOG_ERR, rs, s, "Failed to child-init auditlog mutex");
148         }
149     }
150 }
151
152 /**
153  * Releases resources held by engine instance.
154  */
155 void modsecurity_shutdown(msc_engine *msce) {
156     if (msce == NULL) return;
157 }
158
159 /**
160  *
161  */
162 static apr_status_t modsecurity_tx_cleanup(void *data) {
163     modsec_rec *msr = (modsec_rec *)data;
164     const apr_array_header_t *arr;
165     apr_table_entry_t *te;
166     int collect_garbage = 0;
167     int i;
168     char *my_error_msg = NULL;
169
170     if (msr == NULL) return APR_SUCCESS;
171
172     if (rand() < RAND_MAX/100) {
173         collect_garbage = 1;
174     }
175
176     /* Collections, store & remove stale. */
177     arr = apr_table_elts(msr->collections);
178     te = (apr_table_entry_t *)arr->elts;
179     for (i = 0; i < arr->nelts; i++) {
180         apr_table_t *col = (apr_table_t *)te[i].val;
181
182         /* Only store those collections that changed. */
183         if (apr_table_get(msr->collections_dirty, te[i].key)) {
184             collection_store(msr, col);
185         }
186
187         if (collect_garbage) {
188             collections_remove_stale(msr, te[i].key);
189         }
190     }
191
192     /* Multipart processor cleanup. */
193     if (msr->mpd != NULL) multipart_cleanup(msr);
194
195     /* XML processor cleanup. */
196     if (msr->xml != NULL) xml_cleanup(msr);
197
198     // TODO: Why do we ignore return code here?
199     modsecurity_request_body_clear(msr, &my_error_msg);
200     if (my_error_msg != NULL) {
201         msr_log(msr, 1, "%s", my_error_msg);
202     }
203
204     return APR_SUCCESS;
205 }
206
207 /**
208  *
209  */
210 apr_status_t modsecurity_tx_init(modsec_rec *msr) {
211     const char *s = NULL;
212     const apr_array_header_t *arr;
213     apr_table_entry_t *te;
214     int i;
215
216     /* Register TX cleanup */
217     apr_pool_cleanup_register(msr->mp, msr, modsecurity_tx_cleanup, apr_pool_cleanup_null);
218
219     /* Initialise C-L */
220     msr->request_content_length = -1;
221     s = apr_table_get(msr->request_headers, "Content-Length");
222     if (s != NULL) {
223         msr->request_content_length = strtol(s, NULL, 10);
224     }
225
226     /* Figure out whether this request has a body */
227     msr->reqbody_chunked = 0;
228     msr->reqbody_should_exist = 0;
229     if (msr->request_content_length == -1) {
230         /* There's no C-L, but is chunked encoding used? */
231         char *transfer_encoding = (char *)apr_table_get(msr->request_headers, "Transfer-Encoding");
232         if ((transfer_encoding != NULL)&&(strstr(transfer_encoding, "chunked") != NULL)) {
233             msr->reqbody_should_exist = 1;
234             msr->reqbody_chunked = 1;
235         }
236     } else {
237         /* C-L found */
238         msr->reqbody_should_exist = 1;
239     }
240
241     /* Initialise C-T */
242     msr->request_content_type = NULL;
243     s = apr_table_get(msr->request_headers, "Content-Type");
244     if (s != NULL) msr->request_content_type = s;
245
246     /* Decide what to do with the request body. */
247     if ((msr->request_content_type != NULL)
248        && (strncasecmp(msr->request_content_type, "application/x-www-form-urlencoded", 33) == 0))
249     {
250         /* Always place POST requests with
251          * "application/x-www-form-urlencoded" payloads in memory.
252          */
253         msr->msc_reqbody_storage = MSC_REQBODY_MEMORY;
254         msr->msc_reqbody_spilltodisk = 0;
255         msr->msc_reqbody_processor = "URLENCODED";
256     } else {
257         /* If the C-L is known and there's more data than
258          * our limit go to disk straight away.
259          */
260         if ((msr->request_content_length != -1)
261            && (msr->request_content_length > msr->txcfg->reqbody_inmemory_limit))
262         {
263             msr->msc_reqbody_storage = MSC_REQBODY_DISK;
264         }
265
266         /* In all other cases, try using the memory first
267          * but switch over to disk for larger bodies.
268          */
269         msr->msc_reqbody_storage = MSC_REQBODY_MEMORY;
270         msr->msc_reqbody_spilltodisk = 1;
271
272         if (msr->request_content_type != NULL) {
273             if (strncasecmp(msr->request_content_type, "multipart/form-data", 19) == 0) {
274                 msr->msc_reqbody_processor = "MULTIPART";
275             }
276         }
277     }
278
279     /* Check if we are forcing buffering, then use memory only. */
280     if (msr->txcfg->reqbody_buffering != REQUEST_BODY_FORCEBUF_OFF) {
281         msr->msc_reqbody_storage = MSC_REQBODY_MEMORY;
282         msr->msc_reqbody_spilltodisk = 0;
283     }
284
285     /* Initialise arguments */
286     msr->arguments = apr_table_make(msr->mp, 32);
287     if (msr->arguments == NULL) return -1;
288     if (msr->query_string != NULL) {
289         int invalid_count = 0;
290
291         if (parse_arguments(msr, msr->query_string, strlen(msr->query_string),
292             msr->txcfg->argument_separator, "QUERY_STRING", msr->arguments,
293             &invalid_count) < 0)
294         {
295             msr_log(msr, 1, "Initialisation: Error occurred while parsing QUERY_STRING arguments.");
296             return -1;
297         }
298     }
299
300     msr->arguments_to_sanitise = apr_table_make(msr->mp, 16);
301     if (msr->arguments_to_sanitise == NULL) return -1;
302     msr->request_headers_to_sanitise = apr_table_make(msr->mp, 16);
303     if (msr->request_headers_to_sanitise == NULL) return -1;
304     msr->response_headers_to_sanitise = apr_table_make(msr->mp, 16);
305     if (msr->response_headers_to_sanitise == NULL) return -1;
306
307     /* Initialise cookies */
308     msr->request_cookies = apr_table_make(msr->mp, 16);
309     if (msr->request_cookies == NULL) return -1;
310
311     /* Locate the cookie headers and parse them */
312     arr = apr_table_elts(msr->request_headers);
313     te = (apr_table_entry_t *)arr->elts;
314     for (i = 0; i < arr->nelts; i++) {
315         if (strcasecmp(te[i].key, "Cookie") == 0) {
316             if (msr->txcfg->cookie_format == COOKIES_V0) {
317                 parse_cookies_v0(msr, te[i].val, msr->request_cookies);
318             } else {
319                 parse_cookies_v1(msr, te[i].val, msr->request_cookies);
320             }
321         }
322     }
323
324     /* Collections. */
325     msr->tx_vars = apr_table_make(msr->mp, 32);
326     if (msr->tx_vars == NULL) return -1;
327
328     msr->geo_vars = apr_table_make(msr->mp, 8);
329     if (msr->geo_vars == NULL) return -1;
330
331     msr->collections_original = apr_table_make(msr->mp, 8);
332     if (msr->collections_original == NULL) return -1;
333     msr->collections = apr_table_make(msr->mp, 8);
334     if (msr->collections == NULL) return -1;
335     msr->collections_dirty = apr_table_make(msr->mp, 8);
336     if (msr->collections_dirty == NULL) return -1;
337
338     /* Other */
339     msr->tcache = NULL;
340     msr->tcache_items = 0;
341
342     msr->matched_rules = apr_array_make(msr->mp, 16, sizeof(void *));
343     if (msr->matched_rules == NULL) return -1;
344
345     msr->matched_var = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
346     if (msr->matched_var == NULL) return -1;
347
348     msr->highest_severity = 255; /* high, invalid value */
349
350     msr->removed_rules = apr_array_make(msr->mp, 16, sizeof(char *));
351     if (msr->removed_rules == NULL) return -1;
352
353     return 1;
354 }
355
356 /**
357  *
358  */
359 static int is_response_status_relevant(modsec_rec *msr, int status) {
360     char *my_error_msg = NULL;
361     apr_status_t rc;
362     char buf[32];
363
364     /* ENH: Setting is_relevant here will cause an audit even if noauditlog
365      * was set for the last rule that matched.  Is this what we want?
366      */
367
368     if ((msr->txcfg->auditlog_relevant_regex == NULL)
369         ||(msr->txcfg->auditlog_relevant_regex == NOT_SET_P))
370     {
371         return 0;
372     }
373
374     apr_snprintf(buf, sizeof(buf), "%d", status);
375
376     rc = msc_regexec(msr->txcfg->auditlog_relevant_regex, buf, strlen(buf), &my_error_msg);
377     if (rc >= 0) return 1;
378     if (rc == PCRE_ERROR_NOMATCH) return 0;
379
380     msr_log(msr, 1, "Regex processing failed (rc %d): %s", rc, my_error_msg);
381     return 0;
382 }
383
384 /**
385  *
386  */
387 static apr_status_t modsecurity_process_phase_request_headers(modsec_rec *msr) {
388     msr_log(msr, 4, "Starting phase REQUEST_HEADERS.");
389
390     if (msr->txcfg->ruleset != NULL) {
391         return msre_ruleset_process_phase(msr->txcfg->ruleset, msr);
392     }
393
394     return 0;
395 }
396
397 /**
398  *
399  */
400 static apr_status_t modsecurity_process_phase_request_body(modsec_rec *msr) {
401     if ((msr->allow_scope == ACTION_ALLOW_REQUEST)||(msr->allow_scope == ACTION_ALLOW)) {
402         msr_log(msr, 4, "Skipping phase REQUEST_BODY (allow used).");
403         return 0;
404     } else {
405         msr_log(msr, 4, "Starting phase REQUEST_BODY.");
406     }
407
408     if (msr->txcfg->ruleset != NULL) {
409         return msre_ruleset_process_phase(msr->txcfg->ruleset, msr);
410     }
411
412     return 0;
413 }
414
415 /**
416  *
417  */
418 static apr_status_t modsecurity_process_phase_response_headers(modsec_rec *msr) {
419     if (msr->allow_scope == ACTION_ALLOW) {
420         msr_log(msr, 4, "Skipping phase RESPONSE_HEADERS (allow used).");
421         return 0;
422     } else {
423         msr_log(msr, 4, "Starting phase RESPONSE_HEADERS.");
424     }
425
426     if (msr->txcfg->ruleset != NULL) {
427         return msre_ruleset_process_phase(msr->txcfg->ruleset, msr);
428     }
429
430     return 0;
431 }
432
433 /**
434  *
435  */
436 static apr_status_t modsecurity_process_phase_response_body(modsec_rec *msr) {
437     if (msr->allow_scope == ACTION_ALLOW) {
438         msr_log(msr, 4, "Skipping phase RESPONSE_BODY (allow used).");
439         return 0;
440     } else {
441         msr_log(msr, 4, "Starting phase RESPONSE_BODY.");
442     }
443
444     if (msr->txcfg->ruleset != NULL) {
445         return msre_ruleset_process_phase(msr->txcfg->ruleset, msr);
446     }
447
448     return 0;
449 }
450
451 /**
452  *
453  */
454 static apr_status_t modsecurity_process_phase_logging(modsec_rec *msr) {
455     msr_log(msr, 4, "Starting phase LOGGING.");
456
457     if (msr->txcfg->ruleset != NULL) {
458         msre_ruleset_process_phase(msr->txcfg->ruleset, msr);
459     }
460
461     /* Is this request relevant for logging purposes? */
462     if (msr->is_relevant == 0) {
463         /* Check the status */
464         msr->is_relevant += is_response_status_relevant(msr, msr->r->status);
465
466         /* If we processed two requests and statuses are different then
467          * check the other status too.
468          */
469         if (msr->r_early->status != msr->r->status) {
470             msr->is_relevant += is_response_status_relevant(msr, msr->r_early->status);
471         }
472     }
473
474     /* Figure out if we want to keep the files (if there are any, of course). */
475     if ((msr->txcfg->upload_keep_files == KEEP_FILES_ON)
476         || ((msr->txcfg->upload_keep_files == KEEP_FILES_RELEVANT_ONLY)&&(msr->is_relevant)))
477     {
478         msr->upload_remove_files = 0;
479     } else {
480         msr->upload_remove_files = 1;
481     }
482
483     /* Are we configured for audit logging? */
484     switch(msr->txcfg->auditlog_flag) {
485         case AUDITLOG_OFF :
486             msr_log(msr, 4, "Audit log: Not configured to run for this request.");
487             return DECLINED;
488             break;
489
490         case AUDITLOG_RELEVANT :
491             if (msr->is_relevant == 0) {
492                 msr_log(msr, 4, "Audit log: Ignoring a non-relevant request.");
493                 return DECLINED;
494             }
495             break;
496
497         case AUDITLOG_ON :
498             /* All right, do nothing */
499             break;
500
501         default :
502             msr_log(msr, 1, "Internal error: Could not determine if auditing is needed, so forcing auditing.");
503             break;
504     }
505
506     /* Invoke the Audit logger */
507     msr_log(msr, 4, "Audit log: Logging this transaction.");
508
509     sec_audit_logger(msr);
510
511     return 0;
512 }
513
514 /**
515  * Processes one transaction phase. The phase number does not
516  * need to be explicitly provided since it's already available
517  * in the modsec_rec structure.
518  */
519 apr_status_t modsecurity_process_phase(modsec_rec *msr, unsigned int phase) {
520     /* Check if we should run. */
521     if ((msr->was_intercepted)&&(phase != PHASE_LOGGING)) {
522         msr_log(msr, 4, "Skipping phase %d as request was already intercepted.", phase);
523         return 0;
524     }
525
526     /* Do not process the same phase twice. */
527     if (msr->phase >= phase) {
528         msr_log(msr, 4, "Skipping phase %d because it was previously run (at %d now).",
529             phase, msr->phase);
530         return 0;
531     }
532
533     msr->phase = phase;
534
535     /* Clear out the transformation cache at the start of each phase */
536     if (msr->txcfg->cache_trans == MODSEC_CACHE_ENABLED) {
537         if (msr->tcache) {
538             apr_hash_index_t *hi;
539             void *dummy;
540             apr_table_t *tab;
541             const void *key;
542             apr_ssize_t klen;
543             #ifdef CACHE_DEBUG
544             apr_pool_t *mp = msr->msc_rule_mptmp;
545             const apr_array_header_t *ctarr;
546             const apr_table_entry_t *ctelts;
547             msre_cache_rec *rec;
548             int cn = 0;
549             int ri;
550             #else
551             apr_pool_t *mp = msr->mp;
552             #endif
553
554             for (hi = apr_hash_first(mp, msr->tcache); hi; hi = apr_hash_next(hi)) {
555                 apr_hash_this(hi, &key, &klen, &dummy);
556                 tab = (apr_table_t *)dummy;
557
558                 if (tab == NULL) continue;
559
560                 #ifdef CACHE_DEBUG
561                 /* Dump the cache out as we clear */
562                 ctarr = apr_table_elts(tab);
563                 ctelts = (const apr_table_entry_t*)ctarr->elts;
564                 for (ri = 0; ri < ctarr->nelts; ri++) {
565                     cn++;
566                     rec = (msre_cache_rec *)ctelts[ri].val;
567                     if (rec->changed) {
568                         if (msr->txcfg->debuglog_level >= 9) {
569                             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);
570                         }
571                     }
572                     else {
573                         if (msr->txcfg->debuglog_level >= 9) {
574                             msr_log(msr, 9, "CACHE: %5d) hits=%d key=%pp %x;%s=<no change>", cn, rec->hits, key, rec->num, rec->path);
575                         }
576                     }
577                 }
578                 #endif
579
580                 apr_table_clear(tab);
581                 apr_hash_set(msr->tcache, key, klen, NULL);
582             }
583
584             msr_log(msr, 9, "Cleared transformation cache for phase %d", msr->phase);
585         }
586
587         msr->tcache_items = 0;
588         msr->tcache = apr_hash_make(msr->mp);
589         if (msr->tcache == NULL) return -1;
590     }
591
592     switch(phase) {
593         case 1 :
594             return modsecurity_process_phase_request_headers(msr);
595         case 2 :
596             return modsecurity_process_phase_request_body(msr);
597         case 3 :
598             return modsecurity_process_phase_response_headers(msr);
599         case 4 :
600             return modsecurity_process_phase_response_body(msr);
601         case 5 :
602             return modsecurity_process_phase_logging(msr);
603         default :
604             msr_log(msr, 1, "Invalid processing phase: %d", msr->phase);
605             break;
606     }
607
608     return -1;
609 }