/* * ModSecurity for Apache 2.x, http://www.modsecurity.org/ * Copyright (c) 2002-2007 Breach Security, Inc. (http://www.breach.com) * * You should have received a copy of the licence along with this * program (stored in the file "LICENSE"). If the file is missing, * or if you have any other questions related to the licence, please * write to Breach Security, Inc. at support@breach.com. * */ #include #include #include #include #include #include #ifdef WIN32 #include #else #include #include #endif #if !defined(OS2) && !defined(WIN32) && !defined(BEOS) && !defined(NETWARE) #include "unixd.h" #define __SET_MUTEX_PERMS #endif #include "ap_config.h" #include "httpd.h" #include "http_config.h" #include "http_request.h" #include "http_core.h" #include "http_log.h" #include "http_protocol.h" #include "util_script.h" #include "ap_mpm.h" #include "apr.h" #include "apr_strings.h" #include "apr_hash.h" #include "apr_user.h" #include "apr_lib.h" #include "apr_signal.h" #include "apr_global_mutex.h" #include "apr_md5.h" #if (defined(AP_REG_ICASE)||(MODULE_MAGIC_NUMBER >= 20050127)) #define regex_t ap_regex_t #define regmatch_t ap_regmatch_t #define REG_EXTENDED AP_REG_EXTENDED #define REG_NOSUB AP_REG_NOSUB #define REG_ICASE AP_REG_ICASE #define REG_NOMATCH AP_REG_NOMATCH #endif #if !defined(DISABLE_HTACCESS_CONFIG) #define CMD_SCOPE_ANY OR_OPTIONS #else #define CMD_SCOPE_ANY (RSRC_CONF | ACCESS_CONF) #endif #if !defined(O_BINARY) #define O_BINARY (0) #endif #ifndef PIPE_BUF #define PIPE_BUF (512) #endif module AP_MODULE_DECLARE_DATA security_module; static apr_global_mutex_t *modsec_auditlog_lock = NULL; static ap_filter_rec_t *global_sec_filter_in; static ap_filter_rec_t *global_sec_filter_out; static char *real_server_signature = NULL; #define MODULE_NAME "ModSecurity" #define MODULE_RELEASE "1.9.5" #define MODULE_NAME_FULL (MODULE_NAME " v" MODULE_RELEASE " (Apache 2.x)") #define CREATEMODE ( APR_UREAD | APR_UWRITE | APR_GREAD ) #define CREATEMODE_DIR ( APR_UREAD | APR_UWRITE | APR_UEXECUTE | APR_GREAD | APR_GEXECUTE ) #if defined(NETWARE) #define CREATEMODE_UNISTD ( S_IREAD | S_IWRITE ) #elif defined(WIN32) #define CREATEMODE_UNISTD ( _S_IREAD | _S_IWRITE ) #else #define CREATEMODE_UNISTD ( S_IRUSR | S_IWUSR | S_IRGRP ) #endif #define MODSEC_SKIP -2000 #define MODSEC_ALLOW -2001 #define UNICODE_ERROR_CHARACTERS_MISSING -1 #define UNICODE_ERROR_INVALID_ENCODING -2 #define UNICODE_ERROR_OVERLONG_CHARACTER -3 #define NOT_SET -1 #define NOT_SET_P (void *)-1 #define FILTERING_OFF 0 #define FILTERING_ON 1 #define FILTERING_DYNAMIC_ONLY 2 #define AUDITLOG_OFF 0 #define AUDITLOG_ON 1 #define AUDITLOG_DYNAMIC_OR_RELEVANT 2 #define AUDITLOG_RELEVANT_ONLY 3 #define ACTION_NONE 0 #define ACTION_DENY 1 #define ACTION_REDIRECT 2 #define ACTION_ALLOW 3 #define ACTION_SKIP 4 #define ACTION_PROXY 5 #define VAR_ACTION_ALLOW 1 #define VAR_ACTION_DENY 0 #define VAR_UNKNOWN 0 #define VAR_ARG 1 #define VAR_HEADER 2 #define VAR_ENV 3 #define VAR_ARGS 4 #define VAR_POST_PAYLOAD 5 #define VAR_ARGS_NAMES 6 #define VAR_ARGS_VALUES 7 #define VAR_ARGS_SELECTIVE 8 #define VAR_OUTPUT 9 #define VAR_COOKIES_NAMES 10 #define VAR_COOKIES_VALUES 11 #define VAR_COOKIE 12 #define VAR_HEADERS 13 #define VAR_HEADERS_COUNT 14 #define VAR_HEADERS_NAMES 15 #define VAR_HEADERS_VALUES 16 #define VAR_FILES_COUNT 17 #define VAR_FILES_NAMES 18 #define VAR_FILES_SIZES 19 #define VAR_ARGS_COUNT 20 #define VAR_REMOTE_ADDR 21 #define VAR_REMOTE_HOST 22 #define VAR_REMOTE_USER 23 #define VAR_REMOTE_IDENT 24 #define VAR_REQUEST_METHOD 25 #define VAR_SCRIPT_FILENAME 26 #define VAR_PATH_INFO 27 #define VAR_QUERY_STRING 28 #define VAR_AUTH_TYPE 29 #define VAR_DOCUMENT_ROOT 30 #define VAR_SERVER_ADMIN 31 #define VAR_SERVER_NAME 32 #define VAR_SERVER_ADDR 33 #define VAR_SERVER_PORT 34 #define VAR_SERVER_PROTOCOL 35 #define VAR_SERVER_SOFTWARE 36 #define VAR_TIME_YEAR 37 #define VAR_TIME_MON 38 #define VAR_TIME_DAY 39 #define VAR_TIME_HOUR 40 #define VAR_TIME_MIN 41 #define VAR_TIME_SEC 42 #define VAR_TIME_WDAY 43 #define VAR_TIME 44 #define VAR_API_VERSION 45 #define VAR_THE_REQUEST 46 #define VAR_REQUEST_URI 47 #define VAR_REQUEST_FILENAME 48 #define VAR_IS_SUBREQ 49 #define VAR_HANDLER 50 #define VAR_SCRIPT_UID 51 #define VAR_SCRIPT_GID 52 #define VAR_SCRIPT_USERNAME 53 #define VAR_SCRIPT_GROUPNAME 54 #define VAR_SCRIPT_MODE 55 #define VAR_COOKIES_COUNT 56 #define VAR_FILE_NAME 57 #define VAR_FILE_SIZE 58 #define VAR_OUTPUT_STATUS 59 #define VAR_REQUEST_BASENAME 60 #define VAR_SCRIPT_BASENAME 61 #define MULTIPART_BUF_SIZE 4096 #define REQBODY_FILE_NONE 0 #define REQBODY_FILE_DELETE 1 #define REQBODY_FILE_LEAVE 2 #define MULTIPART_FORMDATA 1 #define MULTIPART_FILE 2 #define POST_ON_DISK 1 #define POST_IN_MEMORY 2 #define COOKIES_V0 0 #define COOKIES_V1 1 #define NOTE_MESSAGE "mod_security-message" #define NOTE_EXECUTED "mod_security-executed" #define NOTE_ACTION "mod_security-action" #define NOTE_MSR "mod_security-msr" #define NOTE_ACTED "mod_security-relevant" #define NOTE_TIME "mod_security-time" #define NOTE_BODY "mod_security-body" #define FATAL_ERROR "Unable to allocate memory" #define UNKNOWN_CSID 0 #define MB_CSID 800 /* First multibyte character set */ #define UNI3_CSID 873 /* Unicode 3.x character set ID */ #define SJIS1_CSID 832 /* SJIS character set ID */ #define SJIS2_CSID 834 /* SJIS+YEN character set ID */ #define BIG5_CSID 865 /* BIG5 character set ID */ #define GBK_CSID 852 /* GBK character set ID */ #define GB2312_CSID 850 /* GB2312 character set ID */ #define ZHT32EUC_CSID 860 /* Chinese 4-byte character set */ #define JEUC1_CSID 830 /* JEUC character set ID */ #define JEUC2_CSID 831 /* JEUC+YEN character set ID */ #define JA16VMS_CSID 829 /* VMS 2-byte Japanese */ #define KEEP_FILES_OFF 0 #define KEEP_FILES_ON 1 #define KEEP_FILES_RELEVANT_ONLY 2 #define INHERITANCE_IMPORT 1 #define INHERITANCE_REMOVE 2 #define PHASE_INPUT 0 #define PHASE_OUTPUT 1 #define AUDITLOG_SERIAL 0 #define AUDITLOG_CONCURRENT 1 #define AUDITLOG_PART_FIRST 'A' #define AUDITLOG_PART_HEADER 'A' #define AUDITLOG_PART_REQUEST_HEADERS 'B' #define AUDITLOG_PART_REQUEST_BODY 'C' #define AUDITLOG_PART_RESPONSE_HEADERS 'D' #define AUDITLOG_PART_RESPONSE_BODY 'E' #define AUDITLOG_PART_A_RESPONSE_HEADERS 'F' #define AUDITLOG_PART_A_RESPONSE_BODY 'G' #define AUDITLOG_PART_TRAILER 'H' #define AUDITLOG_PART_LAST 'H' #define AUDITLOG_PART_ENDMARKER 'Z' #define ABSOLUTE_VALUE 1 #define RELATIVE_VALUE 2 #define RELATIVE_VALUE_POSITIVE 3 #define RELATIVE_VALUE_NEGATIVE 4 static const char * const all_variables[] = { "UNKOWN", "ARG", "HEADER", "ENV", "ARGS", "POST_PAYLOAD", "ARGS_NAMES", "ARGS_VALUES", "ARGS_SELECTIVE", "OUTPUT", "COOKIES_NAMES", /* 10 */ "COOKIES_VALUES", "COOKIE", "HEADERS", "HEADERS_COUNT", "HEADERS_NAMES", "HEADERS_VALUES", "FILES_COUNT", "FILES_NAMES", "FILES_SIZES", "ARGS_COUNT", /* 20 */ "REMOTE_ADDR", "REMOTE_HOST", "REMOTE_USER", "REMOTE_IDENT", "REQUEST_METHOD", "SCRIPT_FILENAME", "PATH_INFO", "QUERY_STRING", "AUTH_TYPE", "DOCUMENT_ROOT", /* 30 */ "SERVER_ADMIN", "SERVER_NAME", "SERVER_ADDR", "SERVER_PORT", "SERVER_PROTOCOL", "SERVER_SOFTWARE", "TIME_YEAR", "TIME_MON", "TIME_DAY", "TIME_HOUR", /* 40 */ "TIME_MIN", "TIME_SEC", "TIME_WDAY", "TIME", "API_VERSION", "THE_REQUEST", "REQUEST_URI", "REQUEST_FILENAME", "IS_SUBREQ", "HANDLER", /* 50 */ "SCRIPT_UID", "SCRIPT_GID", "SCRIPT_USERNAME", "SCRIPT_GROUPNAME", "SCRIPT_MODE", "COOKIES_COUNT", "FILE_NAME", "FILE_SIZE", "OUTPUT_STATUS", "REQUEST_BASENAME", /* 60 */ "SCRIPT_BASENAME", NULL }; static const char * const severities[] = { "EMERGENCY", "ALERT", "CRITICAL", "ERROR", "WARNING", "NOTICE", "INFO", "DEBUG", NULL, }; typedef struct { char *name; int type; int action; } variable; typedef struct { request_rec *r; char *command; char *args; } exec_data; typedef struct { int log; int auditlog; int action; int status; int pause; int skip_count; int is_chained; char *redirect_url; char *proxy_url; int exec; char *exec_string; char *id; char *msg; int severity; char *rev; char *note_name; char *note_value; char *env_name; char *env_value; int mandatory; int logparts; char *logparts_value; } actionset_t; typedef struct sec_dir_config sec_dir_config; typedef struct signature signature; struct signature { actionset_t *actionset; int actions_restricted; char *pattern; regex_t *regex; int is_selective; int is_negative; int is_allow; /* 0 if this is a request rule, 1 is it is a response rule */ int is_output; /* 0 for normal rules, INHERITANCE_IMPORT if this rule is only * a placeholder for a rule that will be imported from the * parent context at runtime, or INHERITANCE_REMOVE if this rule * is only a placeholder for a rule that will be removed from the * configuration at runtime. */ int is_inheritance_placeholder; /* the ID of the rule that is being explicitly imported or removed */ const char *inheritance_id; int requires_parsed_args; apr_array_header_t *variables; signature *first_sig_in_chain; }; struct sec_dir_config { apr_pool_t *p; int filter_engine; int configuration_helper; int scan_post; int scan_output; actionset_t *actionset; actionset_t *actionset_signatures; apr_array_header_t *signatures; char *path; /* -- Audit log -- */ /* Whether audit log should be enabled in the context or not */ int auditlog_flag; /* AUDITLOG_SERIAL (old, default) or AUDITLOG_CONCURRENT (new) */ int auditlog_type; /* The name of the audit log file (for the old type), or the * name of the index file (for the new audit log type) */ char *auditlog_name; /* The file descriptor for the file above */ apr_file_t *auditlog_fd; /* For the new-style audit log only, the path where * audit log entries will be stored */ char *auditlog_storage_dir; /* A list of parts to include in the new-style audit log * entry. By default, it contains 'ABCFHZ'. Have a look at * the AUDITLOG_PART_* constants above to decipher the * meaning. */ char *auditlog_parts; /* A regular expression that determines if a response * status is treated as relevant. */ regex_t *auditlog_relevant_regex; /* -- Debug log -- */ int filter_debug_level; char *debuglog_name; apr_file_t *debuglog_fd; int range_start; int range_end; int check_encoding; int check_unicode_encoding; char *scan_output_mimetypes; char *upload_dir; int upload_keep_files; char *upload_approve_script; int upload_in_memory_limit; int normalize_cookies; int check_cookie_format; int cookie_format; int charset_id; int multibyte_replacement_byte; /* -- Rule inheritance -- */ int filters_clear; /* if set to 1, the rules from this context will * always be inherited (and cannot be removed) by * the children contexts. The default is 0. */ int inheritance_mandatory; /* This array holds the list of mandatory signatures * inherited from the parent context. */ apr_array_header_t *inherited_mandatory_signatures; int actions_restricted; }; /* TODO Since we are not allowing any of these to be configured * on the per-virtual host basis we should simply makes * these variables global. */ typedef struct { int server_response_token; char *chroot_dir; int chroot_completed; char *chroot_lock; char *server_signature; char *guardian_log_name; apr_file_t *guardian_log_fd; char *guardian_log_condition; } sec_srv_config; typedef struct sec_filter_in_ctx_ { char *buffer; int type; /* POST_ON_DISK or POST_IN_MEMORY */ int is_multipart; unsigned long int buflen; /* max. size of the buffer */ unsigned long int bufleft; /* space left in buffer */ unsigned long int sofar; /* data read sofar */ int access_check_performed; apr_bucket_brigade *pbbTmp; char *output_ptr; unsigned long int output_sent; int done_reading; int done_writing; char *tmp_file_name; int tmp_file_fd; int tmp_file_mode; int is_put; } sec_filter_in_ctx; typedef struct modsec_rec modsec_rec; typedef struct sec_filter_out_ctx_ { modsec_rec *msr; char *buffer; unsigned long int buflen; unsigned long int bufused; char *output_ptr; unsigned long int output_sent; char *input_ptr; int done_reading; int done_writing; } sec_filter_out_ctx; typedef struct { /* part type, can be MULTIPART_FORMDATA or MULTIPART_FILE */ int type; /* the name */ char *name; /* variables only, variable value */ char *value; apr_array_header_t *value_parts; /* files only, the content type (where available) */ char *content_type; /* files only, the name of the temporary file holding data */ char *tmp_file_name; int tmp_file_fd; unsigned tmp_file_size; /* files only, filename as supplied by the browser */ char *filename; char *last_header_name; apr_table_t *headers; } multipart_part; typedef struct { modsec_rec *msr; request_rec *r; sec_dir_config *dcfg; apr_pool_t *p; /* this array keeps parts */ apr_array_header_t *parts; /* mime boundary used to detect when * parts end and new begin */ char *boundary; /* internal buffer and other variables * used while parsing */ char buf[MULTIPART_BUF_SIZE + 2]; int buf_contains_line; char *bufptr; int bufleft; /* pointer that keeps track of a part while * it is being built */ multipart_part *mpp; /* part parsing state; 0 means we are reading * headers, 1 means we are collecting data */ int mpp_state; /* because of the way this parsing algorithm * works we hold back the last two bytes of * each data chunk so that we can discard it * later if the next data chunk proves to be * a boundary; the first byte is an indicator * 0 - no content, 1 - two data bytes available */ char reserve[4]; int seen_data; int is_complete; } multipart_data; struct modsec_rec { request_rec *r; char *request_uri; char *_post_payload; char *_fake_post_payload; int should_body_exist; int is_body_read; unsigned long _post_len; int post_payload_dynamic_off; sec_dir_config *dcfg; sec_srv_config *scfg; apr_table_t *parsed_args; apr_table_t *parsed_cookies; char *tmp_message; multipart_data *mpd; /* positive if relevant, zero or negative otherwise */ int is_relevant; /* NOT_SET (-1) do nothing, 0 suppress audit logging, 1 force audit logging */ int explicit_auditlog; int output_filter_complete; /* whether or not the current request is dynamic; this field * is NOT_SET by default because sometimes we don't need to * know (and finding out is expensive). Therefore the possible * values are NOT_SET (we don't know), 0, and 1. */ int is_dynamic; int is_enabled; sec_filter_in_ctx *ctx_in; sec_filter_out_ctx *ctx_out; char *new_auditlog_boundary; char *new_auditlog_filename; apr_file_t *new_auditlog_fd; unsigned int new_auditlog_size; apr_md5_ctx_t new_auditlog_md5ctx; apr_time_t time_checkpoint_1; apr_time_t time_checkpoint_2; apr_time_t time_checkpoint_3; apr_array_header_t *messages; const char *cache_request_uri; const char *cache_path_info; const char *cache_the_request; const char *cache_query_string; const char *cache_request_basename; const char *cache_script_basename; apr_table_t *cache_headers_in; }; typedef struct { char *data; long int length; } data_chunk; static int check_sig_against_string(modsec_rec *msr, signature *_sig, const char *s, int var_type, char *var_name); static void sec_debug_log(request_rec *r, int level, const char *text, ...); static char *normalise_inplace(request_rec *r, sec_dir_config *dcfg, char *uri, char **error_msg); static char *normalise(request_rec *r, sec_dir_config *dcfg, char *_uri, char **error_msg); static char *normalise_relaxed_inplace(request_rec *r, sec_dir_config *dcfg, char *uri, char **error_msg); static char *normalise_relaxed(request_rec *r, sec_dir_config *dcfg, char *_uri, char **error_msg); static char *normalise_other_inplace(request_rec *r, sec_dir_config *dcfg, char *uri, char **error_msg); static char *normalise_urlencoding_relaxed_inplace(request_rec *r, sec_dir_config *dcfg, char *uri, char **error_msg); static char *normalise_urlencoding_inplace(request_rec *r, sec_dir_config *dcfg, char *uri, char **error_msg); static int check_single_signature(modsec_rec *msr, signature *sig); static int _check_single_signature(modsec_rec *msr, signature *sig, char **error_msg); static int parse_cookies(modsec_rec *msr, char **error_msg); static int parse_cookies_v0(modsec_rec *msr, char *cookie_header, char **error_msg); static int parse_cookies_v1(modsec_rec *msr, char *cookie_header, char **error_msg); static char *get_env_var(request_rec *r, char *name); static const char *get_variable(modsec_rec *msr, variable *v, int var_type); static char *remove_binary_content(request_rec *r, char *data, long size); static int parse_arguments(char *s, apr_table_t *parsed_args, request_rec *r, sec_dir_config *dcfg, char **error_msg); static void send_error_bucket(ap_filter_t *f, int status); static char *process_action(char *name, char *value, actionset_t *actionset, apr_pool_t *_pool); static char *parse_actionset(char *p2, actionset_t *actionset, apr_pool_t *_pool); static void sec_set_dir_defaults(sec_dir_config *dcfg); static int detect_unicode_character(request_rec *r, unsigned char *p_read); static modsec_rec *sec_create_context(request_rec *r); static int sec_initialise(modsec_rec *msr); static int sec_check_access(request_rec *r); static int sec_check_all_signatures(modsec_rec *msr); static int verify_uploaded_file(request_rec *r, char *file_path, char *approver_script, char **error_msg); static int multipart_init(multipart_data *mpd, modsec_rec *msr, char **error_msg); static int multipart_complete(multipart_data *mpd, char **error_msg); static apr_status_t multipart_cleanup(void *data); static apr_status_t request_body_file_cleanup(void *data); static int multipart_process_chunk(multipart_data *mpd, const char *buf, unsigned int size, char **error_msg); static int multipart_process_part_header(multipart_data *mpd, char **error_msg); static int multipart_process_part_data(multipart_data *mpd, char **error_msg); static int multipart_parse_content_disposition(multipart_data *mpd, char *value); static char *multipart_construct_filename(multipart_data *mpd); static int multipart_process_boundary(multipart_data *mpd, int last_part, char **error_msg); static int multipart_verify_uploaded_files(request_rec *r, multipart_data *mpd, char *approver_script, char **error_msg); static int multipart_get_variables(multipart_data *mpd, apr_table_t *parsed_args, sec_dir_config *dcfg, char **error_msg); static int multipart_contains_files(multipart_data *mpd); static multipart_part *multipart_get_part(multipart_data *mpd, char *name); static int multipart_check_files_names(modsec_rec *msr, signature *sig, variable *var); static int multipart_check_files_sizes(modsec_rec *msr, signature *sig, variable *var); static char *multipart_reconstruct_urlencoded_body(multipart_data *mpd); static int change_server_signature(server_rec *s, sec_srv_config *scfg); static int sec_mkstemp(char *template); static char *log_escape(apr_pool_t *p, char *text); static char *log_escape_nq(apr_pool_t *p, char *text); static char *log_escape_header_name(apr_pool_t *p, char *text); static char *_log_escape(apr_pool_t *p, char *text, int escape_quotes, int escape_colon); static int convert_charset_to_id(char *name); static char *filter_multibyte_inplace(int cs_id, char replacement_byte, char *outbuf); static char *filter_multibyte_other(int charset_id, char replacement_byte, char *inptr); static char *filter_multibyte_unicode(int charset_id, char replacement_byte, char *inptr); static int sec_exec_child(char *command, const char *argv[], request_rec *r, char **output); static int perform_action(modsec_rec *msr, actionset_t *dcfg_actionset, signature *sig); static char *construct_fake_urlencoded(modsec_rec *msr, apr_table_t *args); static int sec_table_count(apr_table_t *table); static int is_response_status_relevant(request_rec *r, sec_dir_config *dcfg, int status); static char *construct_rule_metadata(modsec_rec *msr, actionset_t *actionset, signature *sig); static void sec_guardian_logger(request_rec *r, request_rec *origr, modsec_rec *msr); static int sec_audit_logger_serial(request_rec *r, request_rec *origr, sec_dir_config *dcfg, modsec_rec *msr); static void sec_audit_logger_concurrent(request_rec *r, request_rec *origr, sec_dir_config *dcfg, modsec_rec *msr); static void sec_auditlog_init(modsec_rec *msr); static int sec_auditlog_write(modsec_rec *msr, char *data, unsigned int len); static void store_msr(request_rec *r, modsec_rec *msr); static modsec_rec *find_msr(request_rec *r); static char *bytes2hex(apr_pool_t *pool, unsigned char *data, int len); static unsigned char x2c(unsigned char *what); static unsigned char *c2x(unsigned what, unsigned char *where); static char *strtolower(char *str); static char *get_temp_folder(apr_pool_t *p); static char *get_file_basename(apr_pool_t *p, char *filename); static char *get_apr_error(apr_pool_t *p, apr_status_t rc); static int sec_copy_file(char *from, char *to); static char *construct_put_filename(modsec_rec *msr); static char *current_logtime(request_rec *r); static char *current_filetime(request_rec *r); static char *construct_auditlog_filename(request_rec *r, char *uniqueid); static char *create_auditlog_boundary(request_rec *r); static void sec_time_checkpoint(modsec_rec *msr, int checkpoint_no); static actionset_t *merge_actionsets(apr_pool_t *pool, actionset_t *parent, actionset_t *child, int actions_restricted); static int sec_remove_lf_crlf_inplace(char *text); static char *strnurlencat(char *destination, char *source, unsigned int maxlen); static int is_token_char(char c); static int is_valid_parts_specification(char *p); static const char *get_response_protocol(request_rec *r); static int parse_severity(char *input); static void init_empty_actionset(actionset_t *actionset); static void init_default_actionset(actionset_t *actionset); /* ----------------------------------------------------------------------------- */ void init_empty_actionset(actionset_t *actionset) { memset(actionset, 0, sizeof(actionset_t)); actionset->action = NOT_SET; actionset->log = NOT_SET; actionset->auditlog = NOT_SET; actionset->status = NOT_SET; actionset->pause = NOT_SET; actionset->exec = NOT_SET; actionset->id = NULL; actionset->rev = NULL; actionset->msg = NULL; actionset->severity = NOT_SET; actionset->skip_count = 1; } void init_default_actionset(actionset_t *actionset) { memset(actionset, 0, sizeof(actionset_t)); actionset->log = 1; actionset->action = ACTION_DENY; actionset->status = HTTP_FORBIDDEN; actionset->auditlog = NOT_SET; } int parse_severity(char *input) { int i = 0; if ((input[0] >= '0')&&(input[0] <= '7')&&(input[1] == '\0')) { return atoi(input); } i = 0; while(severities[i] != NULL) { if (strcmp(severities[i], input) == 0) { return i; } i++; } return -1; } int is_valid_parts_specification(char *p) { char c, *t = p; while((c = *t++) != '\0') { if ((c != AUDITLOG_PART_ENDMARKER)&&((c < AUDITLOG_PART_FIRST)||(c > AUDITLOG_PART_LAST))) { return 0; } } return 1; } int is_token_char(char c) { /* CTLs not allowed */ if ((c <= 32)||(c >= 127)) return 0; switch(c) { case '(' : case ')' : case '<' : case '>' : case '@' : case ',' : case ';' : case ':' : case '\\' : case '"' : case '/' : case '[' : case ']' : case '?' : case '=' : return 0; } return 1; } int sec_remove_lf_crlf_inplace(char *text) { char *p = text; int count = 0; if (text == NULL) return -1; while(*p != '\0') { count++; p++; } if (count > 0) { if (*(p - 1) == '\n') { *(p - 1) = '\0'; if (count > 1) { if (*(p - 2) == '\r') *(p - 2) = '\0'; } } } return 1; } int sec_copy_file(char *from, char *to) { char buf[1025]; int from_fd, to_fd; int i; from_fd = open(from, O_RDONLY); if (from_fd < 0) return -1; to_fd = open(to, O_CREAT | O_EXCL | O_WRONLY, CREATEMODE_UNISTD); if (to_fd < 0) { close(from_fd); return -1; } do { i = read(from_fd, buf, 1024); if (i <= 0) { if ((i == 0)||(i == EINTR)) continue; else { close(from_fd); close(to_fd); return -1; } } else { if (write(to_fd, buf, i) != i) { close(from_fd); close(to_fd); /* TODO remove target file */ return -1; } } } while(i != 0); close(from_fd); close(to_fd); return 1; } /** * Creates a random 8-character string that * consists of hexadecimal numbers, to be used * as an audit log boundary. */ char *create_auditlog_boundary(request_rec *r) { unsigned long data = rand(); return bytes2hex(r->pool, (unsigned char *)&data, 4); } /** * Converts a series of bytes into its hexadecimal * representation. */ char *bytes2hex(apr_pool_t *pool, unsigned char *data, int len) { static unsigned char b2hex[] = "0123456789abcdef"; char *hex = apr_palloc(pool, (len * 2) + 1); int i, j; if (hex == NULL) return NULL; j = 0; for(i = 0; i < len; i++) { hex[j++] = b2hex[data[i] >> 4]; hex[j++] = b2hex[data[i] & 0x0f]; } hex[j] = 0; return hex; } /** * Converts a byte given as its hexadecimal representation * into a proper byte. Does not check for overflows. */ unsigned char x2c(unsigned char *what) { register unsigned char digit; digit = (what[0] >= 'A' ? ((what[0] & 0xdf) - 'A') + 10 : (what[0] - '0')); digit *= 16; digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A') + 10 : (what[1] - '0')); return digit; } /** * Converts a single byte into its hexadecimal representation. * Will overwrite two bytes at the destination. */ unsigned char *c2x(unsigned what, unsigned char *where) { static const char c2x_table[] = "0123456789abcdef"; what = what & 0xff; *where++ = c2x_table[what >> 4]; *where++ = c2x_table[what & 0x0f]; return where; } char *strtolower(char *str) { char *c = str; if (str == NULL) return NULL; while(*c != 0) { *c = tolower(*c); c++; } return str; } char *get_temp_folder(apr_pool_t *p) { char *filename = NULL; #ifdef WIN32 filename = apr_pcalloc(p, 256); if (filename == NULL) return ""; if (GetTempPath(255, filename) != 0) return filename; #endif filename = getenv("TMPDIR"); if (filename != NULL) return filename; filename = getenv("TEMP"); if (filename != NULL) return filename; filename = getenv("TMP"); if (filename != NULL) return filename; #if defined NETWARE return("sys:/tmp/"); #elif defined WIN32 return(""); #else return("/tmp/"); #endif } #ifdef WIN32 char *get_file_basename(apr_pool_t *p, char *filename) { char *b, *c, *d; if (filename == NULL) return NULL; b = apr_pstrdup(p, filename); if (b == NULL) return NULL; c = strrchr(b, '/'); if (c != NULL) { d = strrchr(c, '\\'); if (d != NULL) *d = '\0'; else *c = '\0'; } else { d = strrchr(b, '\\'); if (d != NULL) *d = '\0'; } return b; } #else char *get_file_basename(apr_pool_t *p, char *filename) { char *b, *c; if (filename == NULL) return NULL; b = apr_pstrdup(p, filename); if (b == NULL) return NULL; c = strrchr(b, '/'); if (c != NULL) *c = '\0'; return b; } #endif /** * Returns a new string that contains the error * message for the given return code. */ char *get_apr_error(apr_pool_t *p, apr_status_t rc) { char *text = apr_pcalloc(p, 201); if (text == NULL) return NULL; apr_strerror(rc, text, 200); return text; } /** * Counts the number of entries in the given APR * table. */ int sec_table_count(apr_table_t *table) { const apr_array_header_t *arr; if (table == NULL) return 0; arr = apr_table_elts(table); return arr->nelts; } char *construct_fake_urlencoded(modsec_rec *msr, apr_table_t *args) { apr_table_entry_t *te; const apr_array_header_t *arr; int k; char *body; unsigned int body_len; if (args == NULL) return NULL; /* calculate buffer size */ body_len = 1; arr = apr_table_elts(args); te = (apr_table_entry_t *)arr->elts; for(k = 0; k < arr->nelts; k++) { body_len += 4; body_len += strlen(te[k].key); body_len += strlen(te[k].val); } /* allocate the buffer */ body = apr_palloc(msr->r->pool, body_len + 1); if ((body == NULL)||(body_len + 1 == 0)) return NULL; *body = 0; /* loop through the variables * and create a single string out of them */ arr = apr_table_elts(args); te = (apr_table_entry_t *)arr->elts; for(k = 0; k < arr->nelts; k++) { if (*body != 0) { strncat(body, "&", body_len - strlen(body)); } strncat(body, te[k].key, body_len - strlen(body)); strncat(body, "=", body_len - strlen(body)); strncat(body, te[k].val, body_len - strlen(body)); } return body; } char *strnurlencat(char *destination, char *source, unsigned int maxlen) { char *s = source; char *d = destination; while(*d != '\0') d++; while((*s != '\0')&&(maxlen > 0)) { unsigned char c = *s; if (c == ' ') { *d++ = '+'; maxlen--; } else if ( ((c >= 48)&&(c <= 57)) || ((c >= 65)&&(c <= 90)) || ((c >= 97)&&(c <= 122)) ) { *d++ = c; maxlen--; } else { if (maxlen >= 3) { *d++ = '%'; c2x(c, (unsigned char *)d); d += 2; maxlen -= 3; } else { maxlen = 0; } } s++; } *d++ = '\0'; return destination; } char *construct_rule_metadata(modsec_rec *msr, actionset_t *_actionset, signature *sig) { char *id = "", *rev = "", *msg = "", *severity = ""; actionset_t *actionset = _actionset; /* If we were called because of a match in a rule * that is part of a chain, look up the first rule * in the chain to find the meta data. */ if ((sig != NULL) &&(sig->first_sig_in_chain != NULL) &&(sig->first_sig_in_chain->actionset != NULL) ) { actionset = sig->first_sig_in_chain->actionset; } if (actionset->id != NULL) id = apr_psprintf(msr->r->pool, " [id \"%s\"]", log_escape(msr->r->pool, actionset->id)); if (actionset->rev != NULL) rev = apr_psprintf(msr->r->pool, " [rev \"%s\"]", log_escape(msr->r->pool, actionset->rev)); if (actionset->msg != NULL) msg = apr_psprintf(msr->r->pool, " [msg \"%s\"]", log_escape(msr->r->pool, actionset->msg)); if ((actionset->severity >= 0)&&(actionset->severity <= 7)) severity = apr_psprintf(msr->r->pool, " [severity \"%s\"]", severities[actionset->severity]); return apr_pstrcat(msr->r->pool, id, rev, msg, severity, NULL); } actionset_t *merge_actionsets(apr_pool_t *p, actionset_t *parent, actionset_t *child, int actions_restricted) { actionset_t *actionset = apr_pcalloc(p, sizeof(actionset_t)); if (actionset == NULL) return NULL; /* start with the parent */ memcpy(actionset, parent, sizeof(actionset_t)); /* these actions are always allowed, and can only be set per-rule */ if (child->id != NULL) actionset->id = child->id; if (child->rev != NULL) actionset->rev = child->rev; if (child->msg != NULL) actionset->msg = child->msg; if (child->severity != NOT_SET) actionset->severity = child->severity; if (child->action == ACTION_SKIP) { actionset->action = ACTION_SKIP; actionset->skip_count = child->skip_count; } actionset->is_chained = child->is_chained; /* these actions can be restricted */ if (actions_restricted == 0) { if (child->note_name != NULL) { actionset->note_name = child->note_name; actionset->note_value = child->note_value; } if (child->env_name != NULL) { actionset->env_name = child->env_name; actionset->env_value = child->env_value; } if (child->mandatory) actionset->mandatory = child->mandatory; if (child->log != NOT_SET) actionset->log = child->log; if (child->auditlog != NOT_SET) actionset->auditlog = child->auditlog; if (child->status != NOT_SET) actionset->status = child->status; if (child->pause != NOT_SET) actionset->pause = child->pause; if (child->exec != NOT_SET) { actionset->exec = child->exec; actionset->exec_string = child->exec_string; } if (child->redirect_url != NULL) actionset->redirect_url = child->redirect_url; if (child->proxy_url != NULL) actionset->proxy_url = child->proxy_url; if (child->action != NOT_SET) actionset->action = child->action; if (child->logparts != NOT_SET) { actionset->logparts = child->logparts; actionset->logparts_value = child->logparts_value; } } /* Chained rules must always try to deny * access in order for chaining to work * properly */ if (actionset->is_chained) { actionset->action = ACTION_DENY; actionset->status = HTTP_FORBIDDEN; } return actionset; } int perform_action(modsec_rec *msr, actionset_t *dcfg_actionset, signature *sig) { actionset_t *actionset = dcfg_actionset; char *message = NULL; request_rec *r = msr->r; int log_level = 1; int is_chained = 0; int rc = OK; /* Use the per-signature actionset if available */ if ((sig != NULL)&&(sig->actionset != NULL)) { actionset = sig->actionset; } if (msr->tmp_message == NULL) { msr->tmp_message = "Unknown error"; } /* is audit logging explicitly configured? */ if (actionset->auditlog != NOT_SET) { msr->explicit_auditlog = actionset->auditlog; } if (actionset->log == 0) { /* only change the audit logging setting if it is not set already */ if (msr->explicit_auditlog == NOT_SET) { msr->explicit_auditlog = 0; } log_level = 2; } if (actionset->env_name != NULL) { if (actionset->env_name[0] == '!') { /* delete existing variable */ apr_table_unset(msr->r->subprocess_env, actionset->env_name + 1); } else { /* create new variable */ apr_table_set(msr->r->subprocess_env, actionset->env_name, actionset->env_value); } } if (actionset->note_name != NULL) { if (actionset->note_name[0] == '!') { /* delete existing note */ apr_table_unset(msr->r->notes, actionset->note_name + 1); } else { /* create new note */ apr_table_set(msr->r->notes, actionset->note_name, actionset->note_value); } } /* perform action */ switch(actionset->action) { case ACTION_SKIP : message = apr_psprintf(r->pool, "Skipping %i statements. %s%s", actionset->skip_count, msr->tmp_message, construct_rule_metadata(msr, actionset, sig)); rc = MODSEC_SKIP; break; case ACTION_ALLOW : message = apr_psprintf(r->pool, "Access allowed. %s%s", msr->tmp_message, construct_rule_metadata(msr, actionset, sig)); rc = MODSEC_ALLOW; break; case ACTION_DENY : rc = actionset->status; if (actionset->is_chained) { is_chained = 1; message = apr_psprintf(r->pool, "Warning (chained rule). %s%s", msr->tmp_message, construct_rule_metadata(msr, actionset, sig)); } else { message = apr_psprintf(r->pool, "Access denied with code %i. %s%s", rc, msr->tmp_message, construct_rule_metadata(msr, actionset, sig)); } break; case ACTION_REDIRECT : message = apr_psprintf(r->pool, "Access denied with redirect to [%s]. %s%s", actionset->redirect_url, msr->tmp_message, construct_rule_metadata(msr, actionset, sig)); apr_table_setn(r->headers_out, "Location", actionset->redirect_url); rc = HTTP_MOVED_TEMPORARILY; break; case ACTION_PROXY : if (ap_find_linked_module("mod_proxy.c") == NULL) { sec_debug_log(r, 1, "Proxy action requsted but mod_proxy not found [%s].", actionset->proxy_url); } else { r->filename = apr_psprintf(r->pool, "proxy:%s", actionset->proxy_url); r->proxyreq = PROXYREQ_REVERSE; r->handler = "proxy-server"; rc = OK; } break; case ACTION_NONE : default : message = apr_psprintf(r->pool, "Warning. %s%s", msr->tmp_message, construct_rule_metadata(msr, actionset, sig)); rc = OK; break; } if (is_chained == 0) { sec_debug_log(r, log_level, "%s", message); apr_table_addn(r->headers_in, NOTE_MESSAGE, message); *(char **)apr_array_push(msr->messages) = message; msr->is_relevant++; } else { sec_debug_log(r, 3, "%s", message); } if ((rc != OK)&&(rc != MODSEC_ALLOW)&&(rc != MODSEC_SKIP)) { char *action = apr_psprintf(msr->r->pool, "%i", rc); apr_table_setn(r->headers_in, NOTE_ACTION, action); } /* execute the external script */ if (actionset->exec) { sec_debug_log(r, log_level, "Executing command \"%s\"", log_escape(r->pool, actionset->exec_string)); if (sec_exec_child(actionset->exec_string, NULL, r, NULL)) { char *_temp = apr_psprintf(r->pool, "%s (failed)", actionset->exec_string); apr_table_setn(r->headers_in, NOTE_EXECUTED, _temp); } else { apr_table_setn(r->headers_in, NOTE_EXECUTED, actionset->exec_string); } } if (actionset->pause != 0) { sec_debug_log(r, log_level, "Pausing \"%s\" for %i ms", log_escape(r->pool, r->uri), actionset->pause); /* apr_sleep accepts microseconds */ apr_sleep(actionset->pause * 1000); } /* update audit log parts */ if (actionset->logparts != 0) { if (actionset->logparts == ABSOLUTE_VALUE) { msr->dcfg->auditlog_parts = actionset->logparts_value; } else if (actionset->logparts == RELATIVE_VALUE_POSITIVE) { msr->dcfg->auditlog_parts = apr_pstrcat(r->pool, msr->dcfg->auditlog_parts, actionset->logparts_value, NULL); } else if (actionset->logparts == RELATIVE_VALUE_NEGATIVE) { char c, *t = actionset->logparts_value; while((c = *t++) != '\0') { char *s = msr->dcfg->auditlog_parts; char *d = msr->dcfg->auditlog_parts; while(*s != '\0') { if (*s != c) { *d++ = *s++; } else { s++; } } *d = '\0'; } } sec_debug_log(r, 4, "Using new value for audit log parts: %s", msr->dcfg->auditlog_parts); } msr->tmp_message = NULL; return rc; } int sec_mkstemp(char *template) { #if !(defined(WIN32)||defined(NETWARE)) return mkstemp(template); #else if (mktemp(template) == NULL) return -1; return open(template, O_WRONLY | O_APPEND | O_CREAT | O_BINARY, CREATEMODE_UNISTD); #endif } #ifdef WIN32 char *strtok_r(char *s, const char *sep, char **lasts) { char *sbegin, *send; /* These two parameters must always be valid */ if ((sep == NULL)||(lasts == NULL)) return NULL; /* Either of the following two must not be NULL */ if ((s == NULL)&&(*lasts == NULL)) return NULL; sbegin = s ? s : *lasts; /* Advance through the separator at the beginning */ sbegin += strspn(sbegin, sep); if (*sbegin == '\0') { *lasts = NULL; return NULL; } /* Find the next separator */ send = strpbrk(sbegin, sep); if (send != NULL) *send++ = 0; *lasts = send; return sbegin; } #endif /* * Change the signature of the server if change * was requested in configuration. We do this by * locating the signature in server memory and * writing over it. */ int change_server_signature(server_rec *s, sec_srv_config *scfg) { char *server_version; if (scfg->server_signature == NULL) return 0; server_version = (char *)ap_get_server_version(); if (server_version == NULL) { ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, s, "SecServerSignature: ap_get_server_version returned NULL"); return -1; } if (strlen(server_version) >= strlen(scfg->server_signature)) { strcpy(server_version, scfg->server_signature); return 1; } else { ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, s, "SecServerSignature: the current signature is too short. Please set ServerTokens to Full"); return -1; } } int convert_charset_to_id(char *name) { if (strcasecmp(name, "utf-8") == 0) return UNI3_CSID; if (strcasecmp(name, "shift-jis") == 0) return SJIS1_CSID; if (strcasecmp(name, "shift_jis") == 0) return SJIS2_CSID; if (strcasecmp(name, "big5") == 0) return BIG5_CSID; if (strcasecmp(name, "gbk") == 0) return GBK_CSID; if (strcasecmp(name, "gb2312") == 0) return GB2312_CSID; if (strcasecmp(name, "euc-tw") == 0) return ZHT32EUC_CSID; if (strcasecmp(name, "euc-jp") == 0) return JEUC1_CSID; if (strcasecmp(name, "eucjis") == 0) return JEUC2_CSID; if (strcasecmp(name, "jis0208") == 0) return JA16VMS_CSID; return -1; } char *filter_multibyte_unicode(int charset_id, char replacement_byte, char *inptr) { char *outptr = inptr; int i, j, k, n; i = strlen(inptr); j = 0; /* Unicode */ while(j < i) { k = inptr[j] & 0xFF; if (k < 0x80) { j++; *outptr++ = (char)k; } else if (k < 0xC0) { j++; *outptr++ = replacement_byte; } else { if (k < 0xE0) n = 2; else if (k < 0xF0) n = 3; else if (k < 0xF8) n = 4; else if (k < 0xFC) n = 5; else if (k < 0xFE) n = 6; else n = 1; if (i - j >= n) { j += n; } else { i = j; } *outptr++ = replacement_byte; } } *outptr = 0; return inptr; } char *filter_multibyte_other(int charset_id, char replacement_byte, char *inptr) { char *outptr = inptr; int i, j, k, n; i = strlen(inptr); j = 0; while(j < i) { k = inptr[j] & 0xFF; if (k < 0x80) { j++; *outptr++ = (char)k; } else { n = 2; if ((k == 0x8E)&&(charset_id == ZHT32EUC_CSID)) { n = 4; } else if ((k == 0x8F)&&((charset_id == JEUC1_CSID)||(charset_id == JEUC2_CSID))) { n = 3; } else if ( ((k == 0x80)||(k == 0xFF)) && ((charset_id == BIG5_CSID)||(charset_id == GBK_CSID)||(charset_id == GB2312_CSID)) ) { n = 1; } else if ( ((k == 0x80)||((k >= 0xA0) && (k < 0xE0))) && ((charset_id == SJIS1_CSID)||(charset_id == SJIS2_CSID)) ) { n = 1; } if (i - j >= n) { j += n; } else { i = j; } *outptr++ = (n == 1) ? (char)k : replacement_byte; } } *outptr = 0; return inptr; } char *filter_multibyte_inplace(int charset_id, char replacement_byte, char *inptr) { if (charset_id < MB_CSID) return inptr; /* not multibyte charset */ if (charset_id == UNI3_CSID) return filter_multibyte_unicode(charset_id, replacement_byte, inptr); else return filter_multibyte_other(charset_id, replacement_byte, inptr); } int parse_cookies(modsec_rec *msr, char **error_msg) { char *header = NULL, *header_copy = NULL; if (error_msg == NULL) return -1; *error_msg = NULL; header = (char *)apr_table_get(msr->r->headers_in, "Cookie"); /* Return here if no cookies found */ if (header == NULL) return 0; header_copy = apr_pstrdup(msr->r->pool, header); if (header_copy == NULL) return -1; sec_debug_log(msr->r, 6, "Raw cookie header \"%s\"", log_escape(msr->r->pool, header)); if (msr->dcfg->cookie_format == COOKIES_V0) return parse_cookies_v0(msr, header_copy, error_msg); else if (msr->dcfg->cookie_format == COOKIES_V1) return parse_cookies_v1(msr, header_copy, error_msg); else { *error_msg = apr_psprintf(msr->r->pool, "Unknown cookie format: %i", msr->dcfg->cookie_format); return -1; } } int parse_cookies_v0(modsec_rec *msr, char *cookie_header, char **error_msg) { request_rec *r = msr->r; sec_dir_config *dcfg = msr->dcfg; char *attr_name = NULL, *attr_value = NULL; char *saveptr = NULL; int cookie_count = 0; char *p = NULL; p = strtok_r(cookie_header, ";", &saveptr); while(p != NULL) { attr_name = NULL; attr_value = NULL; /* ignore whitespace at the beginning of cookie name */ while(isspace(*p)) p++; attr_name = p; attr_value = strstr(p, "="); if (attr_value != NULL) { /* terminate cookie name */ *attr_value = 0; /* move over to the beginning of the value */ attr_value++; } if (dcfg->normalize_cookies) { char *my_error_msg = NULL; if (attr_name != NULL) { if (normalise_inplace(r, dcfg, attr_name, &my_error_msg) == NULL) { *error_msg = apr_psprintf(r->pool, "Error normalising cookie name: %s", my_error_msg); return -1; } } if (attr_value != NULL) { if (normalise_inplace(r, dcfg, attr_value, &my_error_msg) == NULL) { *error_msg = apr_psprintf(r->pool, "Error normalising cookie value: %s", my_error_msg); return -1; } } } /* we ignore cookies with empty names */ if ((attr_name != NULL)&&(strlen(attr_name) != 0)) { if (attr_value != NULL) { sec_debug_log(r, 4, "Adding cookie \"%s\"=\"%s\"", log_escape(r->pool, attr_name), log_escape(r->pool, attr_value)); apr_table_add(msr->parsed_cookies, attr_name, attr_value); } else { sec_debug_log(r, 4, "Adding cookie \"%s\" (empty)", log_escape(r->pool, attr_name)); apr_table_add(msr->parsed_cookies, attr_name, ""); } cookie_count++; } p = strtok_r(NULL, ";", &saveptr); } return cookie_count; } int parse_cookies_v1(modsec_rec *msr, char *cookie_header, char **error_msg) { request_rec *r = msr->r; sec_dir_config *dcfg = msr->dcfg; char *attr_name = NULL, *attr_value = NULL, *p = NULL; char *prev_attr_name = NULL; int cookie_count = 0; p = cookie_header; while(*p != 0) { attr_name = NULL; attr_value = NULL; /* attribute name */ /* remove space from the beginning */ while((isspace(*p))&&(*p != 0)) p++; attr_name = p; while((*p != 0)&&(*p != '=')&&(*p != ';')&&(*p != ',')) p++; /* if we've reached the end of string */ if (*p == 0) goto add_cookie; /* if there is no cookie value supplied */ if ((*p == ';')||(*p == ',')) { *p++ = 0; /* terminate the name */ goto add_cookie; } /* terminate the attribute name, * writing over the = character */ *p++ = 0; /* attribute value */ /* skip over the whitespace at the beginning */ while((isspace(*p))&&(*p != 0)) p++; /* no value supplied */ if (*p == 0) goto add_cookie; if (*p == '"') { if (*++p == 0) goto add_cookie; attr_value = p; while((*p != 0)&&(*p != '"')) p++; if (*p != 0) *p++ = 0; else { /* TODO invalid cookie format, missing " at the end * I don't think this is very relevant though */ } } else { attr_value = p; while((*p != 0)&&(*p != ',')&&(*p != ';')) p++; if (*p != 0) *p++ = 0; /* remove the whitespace from the end of cookie value */ if (attr_value != NULL) { char *t = attr_value; int i = 0; while(*t != 0) { t++; i++; } while((i-- > 0)&&(isspace(*(--t)))) *t = 0; } } add_cookie: /* remove the whitespace from the end of cookie name */ if (attr_name != NULL) { char *t = attr_name; int i = 0; while(*t != 0) { t++; i++; } while((i-- > 0)&&(isspace(*(--t)))) *t = 0; } /* perform cookie name & value normalization if requested */ if (dcfg->normalize_cookies) { char *my_error_msg = NULL; if (attr_name != NULL) { if (normalise_inplace(r, dcfg, attr_name, &my_error_msg) == NULL) { *error_msg = apr_psprintf(r->pool, "Error normalising cookie name: %s", my_error_msg); return -1; } } if (attr_value != NULL) { if (normalise_inplace(r, dcfg, attr_value, &my_error_msg) == NULL) { *error_msg = apr_psprintf(r->pool, "Error normalising cookie value: %s", my_error_msg); return -1; } } } /* add the cookie to the list now */ if ((attr_name != NULL)&&(strlen(attr_name) != 0)) { /* handle special attribute names */ if (attr_name[0] == '$') { if (prev_attr_name != NULL) { /* cookie keyword, we change the name we use * so they can have a unique name in the cookie table */ attr_name = apr_psprintf(r->pool, "$%s_%s", prev_attr_name, attr_name + 1); } } if (attr_value != NULL) { sec_debug_log(r, 4, "Adding cookie \"%s\"=\"%s\"", log_escape(r->pool, attr_name), log_escape(r->pool, attr_value)); apr_table_add(msr->parsed_cookies, attr_name, attr_value); } else { sec_debug_log(r, 4, "Adding cookie \"%s\" (empty)", log_escape(r->pool, attr_name)); apr_table_add(msr->parsed_cookies, attr_name, ""); } cookie_count++; /* only keep the cookie names for later */ if (attr_name[0] != '$') prev_attr_name = attr_name; } /* at this point the *p is either 0 (in which case we exit), or * right after the current cookie ended - we need to look for * the next cookie */ while( (*p != 0)&&( (*p == ',')||(*p == ';')||(isspace(*p)) ) ) p++; } return cookie_count; } char *get_env_var(request_rec *r, char *name) { char *result = (char *)apr_table_get(r->notes, name); if (result == NULL) { result = (char *)apr_table_get(r->subprocess_env, name); } if (result == NULL) { result = getenv(name); } return result; } const char *get_variable(modsec_rec *msr, variable *v, int var_type) { request_rec *r = msr->r; sec_dir_config *dcfg_proper = msr->dcfg; sec_dir_config *dcfg = (sec_dir_config *)apr_pcalloc(r->pool, sizeof(sec_dir_config)); char *my_error_msg = NULL; const char *result = NULL; struct tm *tm; time_t tc; /* As of 1.8.6 validation is only done at the beginning of * request processing (and for all request data). Which means * we need to disable it here. Normalisation will be performed * as usual. */ memcpy(dcfg, dcfg_proper, sizeof(sec_dir_config)); dcfg->check_encoding = 0; dcfg->check_unicode_encoding = 0; dcfg->check_cookie_format = 0; dcfg->cookie_format = 0; dcfg->range_start = 0; dcfg->range_end = 255; switch (var_type) { case VAR_ARG : /* we don't normalise parameter values becaue * they are stored normalised */ result = apr_table_get(msr->parsed_args, v->name); break; case VAR_HEADER : result = apr_table_get(msr->cache_headers_in, v->name); break; case VAR_ENV : result = apr_table_get(r->notes, v->name); if (result == NULL) { result = apr_table_get(r->subprocess_env, v->name); } if (result == NULL) { result = getenv(v->name); } break; case VAR_REMOTE_ADDR : result = r->connection->remote_ip; break; case VAR_REMOTE_HOST : result = ap_get_remote_host(r->connection, r->per_dir_config, REMOTE_NAME, NULL); break; case VAR_REMOTE_USER : result = r->user; break; case VAR_REMOTE_IDENT : result = ap_get_remote_logname(r); break; case VAR_REQUEST_METHOD : result = r->method; break; case VAR_REQUEST_URI : if (msr->cache_request_uri != NULL) result = msr->cache_request_uri; else { result = r->unparsed_uri; if (result != NULL) { result = normalise(r, dcfg, (char *)result, &my_error_msg); msr->cache_request_uri = result; } } break; case VAR_AUTH_TYPE : result = r->ap_auth_type; break; case VAR_IS_SUBREQ : result = (r->main != NULL ? "true" : "false"); break; case VAR_DOCUMENT_ROOT : result = ap_document_root(r); break; case VAR_SERVER_ADMIN : result = r->server->server_admin; break; case VAR_SERVER_NAME : result = ap_get_server_name(r); break; case VAR_SERVER_ADDR : result = r->connection->local_ip; break; case VAR_SERVER_PORT : result = apr_psprintf(r->pool, "%i", (int)ap_get_server_port(r)); break; case VAR_SERVER_PROTOCOL : result = r->protocol; break; case VAR_SERVER_SOFTWARE : result = ap_get_server_version(); break; case VAR_API_VERSION : result = apr_psprintf(r->pool, "%d:%d", MODULE_MAGIC_NUMBER_MAJOR, MODULE_MAGIC_NUMBER_MINOR); break; case VAR_TIME_YEAR : tc = time(NULL); tm = localtime(&tc); result = apr_psprintf(r->pool, "%02d%02d", (tm->tm_year / 100) + 19, tm->tm_year % 100); break; case VAR_TIME : tc = time(NULL); tm = localtime(&tc); result = apr_psprintf(r->pool, "%02d%02d%02d%02d%02d%02d%02d", (tm->tm_year / 100) + 19, (tm->tm_year % 100), tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); break; case VAR_TIME_WDAY : tc = time(NULL); tm = localtime(&tc); result = apr_psprintf(r->pool, "%d", tm->tm_wday); break; case VAR_TIME_SEC : tc = time(NULL); tm = localtime(&tc); result = apr_psprintf(r->pool, "%02d", tm->tm_sec); break; case VAR_TIME_MIN : tc = time(NULL); tm = localtime(&tc); result = apr_psprintf(r->pool, "%02d", tm->tm_min); break; case VAR_TIME_HOUR : tc = time(NULL); tm = localtime(&tc); result = apr_psprintf(r->pool, "%02d", tm->tm_hour); break; case VAR_TIME_MON : tc = time(NULL); tm = localtime(&tc); result = apr_psprintf(r->pool, "%02d", tm->tm_mon + 1); break; case VAR_TIME_DAY : tc = time(NULL); tm = localtime(&tc); result = apr_psprintf(r->pool, "%02d", tm->tm_mday); break; case VAR_SCRIPT_FILENAME : case VAR_REQUEST_FILENAME : result = r->filename; break; case VAR_PATH_INFO : if (msr->cache_path_info != NULL) result = msr->cache_path_info; else { result = r->path_info; if (result != NULL) { result = normalise(r, dcfg, (char *)result, &my_error_msg); msr->cache_path_info = result; } } break; case VAR_THE_REQUEST : if (msr->cache_the_request != NULL) result = msr->cache_the_request; else { result = r->the_request; if (result != NULL) { result = normalise(r, dcfg, (char *)result, &my_error_msg); msr->cache_the_request = result; } } break; case VAR_QUERY_STRING : if (msr->cache_query_string != NULL) result = msr->cache_query_string; else { result = r->args; if (result != NULL) { result = normalise(r, dcfg, (char *)result, &my_error_msg); msr->cache_query_string = result; } } break; case VAR_HANDLER : result = r->handler; break; case VAR_COOKIE : /* cookies were escaped earlier */ result = apr_table_get(msr->parsed_cookies, v->name); break; case VAR_SCRIPT_UID : result = apr_psprintf(r->pool, "%i", r->finfo.user); break; case VAR_SCRIPT_GID : result = apr_psprintf(r->pool, "%i", r->finfo.group); break; case VAR_SCRIPT_USERNAME : apr_uid_name_get((char **)&result, r->finfo.user, r->pool); break; case VAR_SCRIPT_GROUPNAME : apr_gid_name_get((char **)&result, r->finfo.group, r->pool); break; case VAR_SCRIPT_MODE : result = apr_psprintf(r->pool, "%04x", r->finfo.protection); break; case VAR_HEADERS_COUNT : result = apr_psprintf(r->pool, "%i", sec_table_count(msr->cache_headers_in)); break; case VAR_FILES_COUNT : if (msr->mpd != NULL) result = apr_psprintf(r->pool, "%i", multipart_contains_files(msr->mpd)); else result = "0"; break; case VAR_ARGS_COUNT : result = apr_psprintf(r->pool, "%i", sec_table_count(msr->parsed_args)); break; case VAR_COOKIES_COUNT : result = apr_psprintf(r->pool, "%i", sec_table_count(msr->parsed_cookies)); break; case VAR_FILE_NAME : if (v->name == NULL) { sec_debug_log(r, 1, "get_variable: Variable FILE_NAME requires name"); } else if (msr->mpd != NULL) { multipart_part *part = multipart_get_part(msr->mpd, v->name); if ((part != NULL)&&(part->type == MULTIPART_FILE)&&(part->filename != NULL)) result = apr_pstrdup(r->pool, part->filename); } break; case VAR_FILE_SIZE : if (v->name == NULL) { sec_debug_log(r, 1, "get_variable: Variable FILE_SIZE requires name"); } else if (msr->mpd != NULL) { multipart_part *part = multipart_get_part(msr->mpd, v->name); if ((part != NULL)&&(part->type == MULTIPART_FILE)&&(part->filename != NULL)) result = apr_psprintf(r->pool, "%u", part->tmp_file_size); } break; case VAR_REQUEST_BASENAME : if (msr->cache_request_basename == NULL) { char *p = NULL, *path = r->parsed_uri.path; if (path != NULL) { p = strrchr(path, '/'); if (p != NULL) path = p + 1; p = strrchr(path, '\\'); if (p != NULL) path = p + 1; msr->cache_request_basename = normalise(r, dcfg, (char *)path, &my_error_msg); } } result = msr->cache_request_basename; break; case VAR_SCRIPT_BASENAME : if (msr->cache_script_basename == NULL) { char *p = NULL, *path = r->filename; if (path != NULL) { p = strrchr(path, '/'); if (p != NULL) path = p + 1; p = strrchr(path, '\\'); if (p != NULL) path = p + 1; msr->cache_script_basename = normalise(r, dcfg, (char *)path, &my_error_msg); } } result = msr->cache_script_basename; break; default : sec_debug_log(r, 1, "get_variable: unresolved variable type %i (internal error)", var_type); break; } if (result == NULL) { result = ""; } return result; } static void *sec_create_srv_config(apr_pool_t *p, server_rec *s) { sec_srv_config *scfg = (sec_srv_config *)apr_pcalloc(p, sizeof(*scfg)); if (scfg == NULL) return NULL; #ifdef CONF_DEBUG fprintf(stdout, "sec_create_srv_config: server_hostname=%s, new cfg=%x\n", s->server_hostname, (unsigned int)scfg); #endif scfg->server_response_token = 0; scfg->chroot_dir = NULL; scfg->server_signature = NULL; scfg->chroot_completed = 0; scfg->chroot_lock = ap_server_root_relative(p, "logs/modsec_chroot.lock"); scfg->guardian_log_name = NULL; scfg->guardian_log_fd = NULL; scfg->guardian_log_condition = NULL; return scfg; } static void *sec_merge_srv_config(apr_pool_t *p, void *_parent, void *_child) { sec_srv_config *parent = (sec_srv_config *)_parent; sec_srv_config *new = (sec_srv_config *)apr_pcalloc(p, sizeof(sec_srv_config)); if (new == NULL) return NULL; #ifdef CONF_DEBUG fprintf(stdout, "sec_merge_srv_config: parent=%x, child=%x, new=%x\n", (unsigned int)_parent, (unsigned int)_child, (unsigned int)new); #endif new->server_signature = parent->server_signature; return new; } void sec_set_dir_defaults(sec_dir_config *dcfg) { if (dcfg == NULL) return; /* return immediatelly if we've already been here */ if (dcfg->configuration_helper == 1) return; dcfg->configuration_helper = 1; if (dcfg->filter_engine == NOT_SET) dcfg->filter_engine = 0; if (dcfg->scan_output == NOT_SET) dcfg->scan_output = 0; if (dcfg->scan_output_mimetypes == NOT_SET_P) dcfg->scan_output_mimetypes = " (null) text/plain text/html "; if (dcfg->scan_post == NOT_SET) dcfg->scan_post = 0; if (dcfg->auditlog_flag == NOT_SET) dcfg->auditlog_flag = 0; if (dcfg->filter_debug_level == NOT_SET) dcfg->filter_debug_level = 0; if (dcfg->filters_clear == NOT_SET) dcfg->filters_clear = 0; if (dcfg->actionset == NOT_SET_P) { dcfg->actionset = (actionset_t *)apr_pcalloc(dcfg->p, sizeof(actionset_t)); init_default_actionset(dcfg->actionset); } if (dcfg->auditlog_name == NOT_SET_P) dcfg->auditlog_name = NULL; if (dcfg->debuglog_name == NOT_SET_P) dcfg->debuglog_name = NULL; if (dcfg->range_start == NOT_SET) dcfg->range_start = 0; if (dcfg->range_end == NOT_SET) dcfg->range_end = 255; if (dcfg->check_encoding == NOT_SET) dcfg->check_encoding = 0; if (dcfg->check_unicode_encoding == NOT_SET) dcfg->check_unicode_encoding = 0; if (dcfg->upload_dir == NOT_SET_P) dcfg->upload_dir = NULL; if (dcfg->upload_keep_files == NOT_SET) dcfg->upload_keep_files = KEEP_FILES_OFF; if (dcfg->upload_approve_script == NOT_SET_P) dcfg->upload_approve_script = NULL; if (dcfg->upload_in_memory_limit == NOT_SET) dcfg->upload_in_memory_limit = 65535; if (dcfg->normalize_cookies == NOT_SET) dcfg->normalize_cookies = 0; if (dcfg->check_cookie_format == NOT_SET) dcfg->check_cookie_format = 0; if (dcfg->cookie_format == NOT_SET) dcfg->cookie_format = COOKIES_V0; if (dcfg->charset_id == NOT_SET) dcfg->charset_id = UNKNOWN_CSID; if (dcfg->multibyte_replacement_byte == NOT_SET) dcfg->multibyte_replacement_byte = 0x0A; /* Note that the code below does not work as expected for us because we are * using dynamic, per-request, configuration finalisation, but configuration * merging takes place before request is processed. */ /* if (dcfg->inheritance_mandatory == NOT_SET) dcfg->inheritance_mandatory = 0; */ if (dcfg->auditlog_type == NOT_SET) dcfg->auditlog_type = AUDITLOG_SERIAL; if (dcfg->auditlog_storage_dir == NOT_SET_P) dcfg->auditlog_storage_dir = NULL; if (dcfg->auditlog_parts == NOT_SET_P) dcfg->auditlog_parts = "ABCFHZ"; if (dcfg->auditlog_relevant_regex == NOT_SET_P) dcfg->auditlog_relevant_regex = NULL; } static void *sec_create_dir_config(apr_pool_t *p, char *path) { sec_dir_config *dcfg = (sec_dir_config *)apr_pcalloc(p, sizeof(*dcfg)); if (dcfg == NULL) return NULL; #ifdef CONF_DEBUG fprintf(stdout, "sec_create_dir_config: %s, new dcfg=%x\n", path, (unsigned int)dcfg); #endif dcfg->p = p; dcfg->configuration_helper = NOT_SET; dcfg->filter_engine = NOT_SET; dcfg->scan_post = NOT_SET; dcfg->scan_output = NOT_SET; dcfg->scan_output_mimetypes = NOT_SET_P; dcfg->actionset = NOT_SET_P; dcfg->signatures = apr_array_make(p, 10, sizeof(signature *)); dcfg->inherited_mandatory_signatures = apr_array_make(p, 10, sizeof(signature *)); if (path == NULL) { dcfg->path = apr_pstrdup(p, "(null)"); } else { dcfg->path = apr_pstrdup(p, path); } dcfg->auditlog_flag = NOT_SET; dcfg->auditlog_name = NOT_SET_P; dcfg->auditlog_fd = NOT_SET_P; dcfg->filter_debug_level = NOT_SET; dcfg->filters_clear = NOT_SET; dcfg->debuglog_name = NOT_SET_P; dcfg->debuglog_fd = NOT_SET_P; dcfg->range_start = NOT_SET; dcfg->range_end = NOT_SET; dcfg->check_encoding = NOT_SET; dcfg->check_unicode_encoding = NOT_SET; dcfg->upload_dir = NOT_SET_P; dcfg->upload_keep_files = NOT_SET; dcfg->upload_approve_script = NOT_SET_P; dcfg->upload_in_memory_limit = NOT_SET; dcfg->normalize_cookies = NOT_SET; dcfg->check_cookie_format = NOT_SET; dcfg->cookie_format = NOT_SET; dcfg->charset_id = NOT_SET; dcfg->multibyte_replacement_byte = NOT_SET; dcfg->inheritance_mandatory = NOT_SET; dcfg->auditlog_type = NOT_SET; dcfg->auditlog_storage_dir = NOT_SET_P; dcfg->auditlog_parts = NOT_SET_P; dcfg->auditlog_relevant_regex = NOT_SET_P; dcfg->actions_restricted = 0; dcfg->actionset_signatures = NOT_SET_P; return dcfg; } static void sec_merge_dir_config_inheritance(apr_pool_t *p, sec_dir_config *parent, sec_dir_config *child, sec_dir_config *new) { /* initialise the signature structures */ new->signatures = apr_array_make(p, 10, sizeof(signature *)); new->inherited_mandatory_signatures = apr_array_make(p, 10, sizeof(signature *)); /* the following two settings are not inherited from the * parent context */ new->filters_clear = child->filters_clear; new->inheritance_mandatory = child->inheritance_mandatory; /* build a list of mandatory signatures */ /* Note that valid values for inheritance_mandatory are -1 and 0 */ if (parent->inheritance_mandatory > 0) { signature **psignatures; int i; /* add all parent signatures to the list */ psignatures = (signature **)parent->signatures->elts; for (i = 0; i < parent->signatures->nelts; i++) { /* ignore placeholders */ if (psignatures[i]->is_inheritance_placeholder == 0) { *(signature **)apr_array_push(new->inherited_mandatory_signatures) = psignatures[i]; } } } else { signature **psignatures; int i = 0, in_chain = 0; /* add parent mandatory signatures to the list */ apr_array_cat(new->inherited_mandatory_signatures, parent->inherited_mandatory_signatures); /* loop through parent signatures and add mandatory ones to the list */ psignatures = (signature **)parent->signatures->elts; for (i = 0; i < parent->signatures->nelts; i++) { /* ignore placeholders and signatures that are really something else */ if ((psignatures[i]->is_inheritance_placeholder == 0) && ((in_chain)||((psignatures[i]->actionset != NULL)&&(psignatures[i]->actionset->mandatory != 0))) ) { *(signature **)apr_array_push(new->inherited_mandatory_signatures) = psignatures[i]; if ((psignatures[i]->actionset != NULL)&&(psignatures[i]->actionset->is_chained)) in_chain = 1; else in_chain = 0; } } } /* is inheritance enabled? */ if (new->filters_clear != 1) { /* inheritance should work here, so add all parent signatures */ apr_array_cat(new->signatures, parent->signatures); } else { signature **psignatures, **msignatures; int i, j, in_chain = 0; /* inheritance is off here, add only the signatures that are mandatory */ psignatures = (signature **)parent->signatures->elts; for (i = 0; i < parent->signatures->nelts; i++) { if (psignatures[i]->is_inheritance_placeholder == 0) { if (in_chain) { *(signature **)apr_array_push(new->signatures) = psignatures[i]; if ((psignatures[i]->actionset != NULL)&&(psignatures[i]->actionset->is_chained)) in_chain = 1; else in_chain = 0; } else { msignatures = (signature **)new->inherited_mandatory_signatures->elts; for (j = 0; j < new->inherited_mandatory_signatures->nelts; j++) { if (psignatures[i] == msignatures[j]) { *(signature **)apr_array_push(new->signatures) = psignatures[i]; if ((psignatures[i]->actionset != NULL)&&(psignatures[i]->actionset->is_chained)) in_chain = 1; else in_chain = 0; break; } } } } } } /* processing the child context now: add proper signatures to the list, process * placeholders to import and remove rules where possible */ { signature **csignatures, **nsignatures, **psignatures, **msignatures; int i, j, k; csignatures = (signature **)child->signatures->elts; for (i = 0; i < child->signatures->nelts; i++) { if (csignatures[i]->is_inheritance_placeholder != 0) { /* not a signature, this is a placeholder */ if (csignatures[i]->is_inheritance_placeholder == INHERITANCE_IMPORT) { signature *new_signature = NULL; int is_present = 0; /* import the signature from the parent context */ /* use the ID to find the signature in the parent context */ psignatures = (signature **)parent->signatures->elts; for(k = 0; k < parent->signatures->nelts; k++) { if ((psignatures[k]->actionset != NULL) && (psignatures[k]->actionset->id != NULL) && (strcasecmp(psignatures[k]->actionset->id, csignatures[i]->inheritance_id) == 0)) { new_signature = psignatures[k]; break; } } if (new_signature != NULL) { /* is the signature already present in this context */ is_present = 0; nsignatures = (signature **)new->signatures->elts; for (j = 0; j < new->signatures->nelts; j++) { if (new_signature == nsignatures[j]) { is_present = 1; break; } } /* finally, add the signature but only if it is not already present */ if (is_present == 0) { /* start with the signature we've just found, and include either * just the signature, or all signatures that are part of the * same chain */ psignatures = (signature **)parent->signatures->elts; for (j = k; j < parent->signatures->nelts; j++) { /* ignore placeholders */ if (psignatures[j]->is_inheritance_placeholder != 0) continue; *(signature **)apr_array_push(new->signatures) = psignatures[j]; if ((psignatures[j]->actionset == NULL)||(psignatures[j]->actionset->is_chained == 0)) break; } } } } else { /* remove signature */ /* find the signature in the new context */ nsignatures = (signature **)new->signatures->elts; for (j = 0; j < new->signatures->nelts; j++) { if ((nsignatures[j]->actionset != NULL) && (nsignatures[j]->actionset->id != NULL) && (strcasecmp(nsignatures[j]->actionset->id, csignatures[i]->inheritance_id) == 0)) { int is_present = 0; /* remove it only if it is not on the mandatory list */ msignatures = (signature **)new->inherited_mandatory_signatures->elts; for (k = 0; k < new->inherited_mandatory_signatures->nelts; k++) { if (msignatures[k] == nsignatures[j]) { is_present = 1; break; } } if (is_present == 0) { signature **tsignatures = NULL; int l, pos = j, in_chain = 1; /* At this point "pos" contains the position of the * signature we wish to remove. This can be either * a standalone signature, or one that starts a * chain. */ tsignatures = (signature **)new->signatures->elts; while((in_chain)&&(pos < new->signatures->nelts)) { /* determine if the signature on the current position is chained */ if ((tsignatures[pos]->actionset != NULL)&&(tsignatures[pos]->actionset->is_chained)) in_chain = 1; else in_chain = 0; /* remove the signature on the current position */ for (l = pos; l < new->signatures->nelts - 1; l++) { tsignatures[l] = tsignatures[l + 1]; } new->signatures->nelts--; } } } } } } else { /* this is a normal signature so just add it do the new context */ *(signature **)apr_array_push(new->signatures) = csignatures[i]; } } } } static void *sec_merge_dir_config(apr_pool_t *p, void *_parent, void *_child) { sec_dir_config *parent = (sec_dir_config *)_parent; sec_dir_config *child = (sec_dir_config *)_child; sec_dir_config *new = (sec_dir_config *)apr_pcalloc(p, sizeof(*new)); if (new == NULL) return NULL; #ifdef CONF_DEBUG fprintf(stdout, "sec_merge_dir_config: parent=%s (%x), child=%s (%x), new (%x)\n", parent->path, (unsigned int)parent, child->path, (unsigned int)child, (unsigned int)new); #endif /* merge the child & parent contexts into a new context */ memcpy(new, child, sizeof(*child)); new->filter_engine = (child->filter_engine == NOT_SET) ? parent->filter_engine : child->filter_engine; new->scan_post = (child->scan_post == NOT_SET) ? parent->scan_post : child->scan_post; new->actionset = (child->actionset == NOT_SET_P) ? parent->actionset : child->actionset; /* actions_restricted not inherited */ /* actionset_signatures not inherited */ /* take care of signature inheritance */ sec_merge_dir_config_inheritance(p, parent, child, new); new->auditlog_flag = (child->auditlog_flag == NOT_SET) ? parent->auditlog_flag : child->auditlog_flag; if (child->auditlog_fd == NOT_SET_P) { new->auditlog_fd = parent->auditlog_fd; new->auditlog_name = parent->auditlog_name; } else { new->auditlog_fd = child->auditlog_fd; new->auditlog_name = child->auditlog_name; } new->auditlog_type = (child->auditlog_type == NOT_SET) ? parent->auditlog_type : child->auditlog_type; new->auditlog_storage_dir = (child->auditlog_storage_dir == NOT_SET_P) ? parent->auditlog_storage_dir : child->auditlog_storage_dir; new->auditlog_parts = (child->auditlog_parts == NOT_SET_P) ? parent->auditlog_parts : child->auditlog_parts; new->filter_debug_level = (child->filter_debug_level == NOT_SET) ? parent->filter_debug_level : child->filter_debug_level; if (child->debuglog_fd == NOT_SET_P) { new->debuglog_fd = parent->debuglog_fd; new->debuglog_name = parent->debuglog_name; } else { new->debuglog_fd = child->debuglog_fd; new->debuglog_name = child->debuglog_name; } new->range_start = (child->range_start == NOT_SET) ? parent->range_start : child->range_start; new->range_end = (child->range_end == NOT_SET) ? parent->range_end : child->range_end; new->check_encoding = (child->check_encoding == NOT_SET) ? parent->check_encoding : child->check_encoding; new->check_unicode_encoding = (child->check_unicode_encoding == NOT_SET) ? parent->check_unicode_encoding : child->check_unicode_encoding; new->upload_dir = (child->upload_dir == NOT_SET_P) ? parent->upload_dir : child->upload_dir; new->upload_keep_files = (child->upload_keep_files == NOT_SET) ? parent->upload_keep_files : child->upload_keep_files; new->upload_approve_script = (child->upload_approve_script == NOT_SET_P) ? parent->upload_approve_script : child->upload_approve_script; new->normalize_cookies = (child->normalize_cookies == NOT_SET) ? parent->normalize_cookies : child->normalize_cookies; new->check_cookie_format = (child->check_cookie_format == NOT_SET) ? parent->check_cookie_format : child->check_cookie_format; new->cookie_format = (child->cookie_format == NOT_SET) ? parent->cookie_format : child->cookie_format; new->charset_id = (child->charset_id == NOT_SET) ? parent->charset_id : child->charset_id; new->multibyte_replacement_byte = (child->multibyte_replacement_byte == NOT_SET) ? parent->multibyte_replacement_byte : child->multibyte_replacement_byte; new->scan_output = (child->scan_output == NOT_SET) ? parent->scan_output : child->scan_output; new->scan_output_mimetypes = (child->scan_output_mimetypes == NOT_SET_P) ? parent->scan_output_mimetypes : child->scan_output_mimetypes; return new; } int detect_unicode_character(request_rec *r, unsigned char *p_read) { int unicode_len = 0; unsigned int d = 0; unsigned char c; if (p_read == NULL) return 0; c = *p_read; if (c == 0) return 0; if ((c & 0xE0) == 0xC0) { /* two byte unicode */ if (*(p_read + 1) == 0) unicode_len = UNICODE_ERROR_CHARACTERS_MISSING; else if (((*(p_read + 1)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; else { unicode_len = 2; d = ((c & 0x1F) << 6) | (*(p_read + 1) & 0x3F); } } else if ((c & 0xF0) == 0xE0) { /* three byte unicode */ if ((*(p_read + 1) == 0)||(*(p_read + 2) == 0)) unicode_len = UNICODE_ERROR_CHARACTERS_MISSING; else if (((*(p_read + 1)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; else if (((*(p_read + 2)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; else { unicode_len = 3; d = ((c & 0x0F) << 12) | ((*(p_read + 1) & 0x3F) << 6) | (*(p_read + 2) & 0x3F); } } else if ((c & 0xF8) == 0xF0) { /* four byte unicode */ if ((*(p_read + 1) == 0)||(*(p_read + 2) == 0)||(*(p_read + 3) == 0)) unicode_len = UNICODE_ERROR_CHARACTERS_MISSING; else if (((*(p_read + 1)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; else if (((*(p_read + 2)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; else if (((*(p_read + 3)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; else { d = ((c & 0x07) << 18) | ((*(p_read + 1) & 0x3F) << 12) | ((*(p_read + 2) & 0x3F) < 6) | (*(p_read + 3) & 0x3F); unicode_len = 4; } } else if ((c & 0xFC) == 0xF8) { /* five byte unicode */ if ((*(p_read + 1) == 0)||(*(p_read + 2) == 0)||(*(p_read + 3) == 0)||(*(p_read + 4) == 0)) unicode_len = UNICODE_ERROR_CHARACTERS_MISSING; else if (((*(p_read + 1)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; else if (((*(p_read + 2)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; else if (((*(p_read + 3)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; else if (((*(p_read + 4)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; else { d = ((c & 0x03) << 24) | ((*(p_read + 1) & 0x3F) << 18) | ((*(p_read + 2) & 0x3F) << 12) | ((*(p_read + 3) & 0x3F) << 6) | (*(p_read + 4) & 0x3F); unicode_len = 5; } } else if ((c & 0xFE) == 0xFC) { /* six byte unicode */ if ((*(p_read + 1) == 0)||(*(p_read + 2) == 0)||(*(p_read + 3) == 0)||(*(p_read + 4) == 0)||(*(p_read + 5) == 0)) unicode_len = UNICODE_ERROR_CHARACTERS_MISSING; else if (((*(p_read + 1)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; else if (((*(p_read + 2)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; else if (((*(p_read + 3)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; else if (((*(p_read + 4)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; else if (((*(p_read + 5)) & 0xC0) != 0x80) unicode_len = UNICODE_ERROR_INVALID_ENCODING; else { d = ((c & 0x01) << 30) | ((*(p_read + 1) & 0x3F) << 24) | ((*(p_read + 2) & 0x3F) << 18) | ((*(p_read + 3) & 0x3F) << 12) | ((*(p_read + 4) & 0x3F) << 6) | (*(p_read + 5) & 0x3F); unicode_len = 6; } } if ((unicode_len > 1)&&((d & 0x7F) == d)) { unicode_len = UNICODE_ERROR_OVERLONG_CHARACTER; } return(unicode_len); } char *normalise_urlencoding_relaxed_inplace(request_rec *r, sec_dir_config *dcfg, char *uri, char **error_msg) { unsigned char *p_read, *p_write; unsigned char c; if (error_msg == NULL) return NULL; *error_msg = NULL; if (uri == NULL) return NULL; p_read = (unsigned char *)uri; p_write = (unsigned char *)uri; while ((c = *p_read) != 0) { if (c == '%') { /* see if there are enough bytes available */ if ((*(p_read + 1) == 0)||(*(p_read + 2) == 0)) { c = 0; } else { /* here we only decode a %xx combo if it is a valid * encoding, we leave it as is otherwise */ char c1 = *(p_read + 1), c2 = *(p_read + 2); if ( (((c1 >= '0')&&(c1 <= '9')) || ((c1 >= 'a')&&(c1 <= 'f')) || ((c1 >= 'A')&&(c1 <= 'F'))) && (((c2 >= '0')&&(c2 <= '9')) || ((c2 >= 'a')&&(c2 <= 'f')) || ((c2 >= 'A')&&(c2 <= 'F'))) ) { c = x2c(++p_read); p_read++; } } } else { /* this check is performed only against the original data * and not against the decoded values (we want to * avoid false positives) */ if ((c < dcfg->range_start)||(c > dcfg->range_end)) { *error_msg = apr_psprintf(r->pool, "Invalid character detected [%i]", c); return NULL; } } /* replace null bytes with whitespace */ if (c == 0) c = 32; *p_write++ = c; p_read++; } *p_write = 0; return uri; } char *normalise_urlencoding_inplace(request_rec *r, sec_dir_config *dcfg, char *uri, char **error_msg) { unsigned char *p_read, *p_write; unsigned char c; if (error_msg == NULL) return NULL; *error_msg = NULL; if (uri == NULL) return NULL; p_read = (unsigned char *)uri; p_write = (unsigned char *)uri; while ((c = *p_read) != 0) { /* decode URL decoding */ if (c == '+') c = 32; else if (c == '%') { /* see if there are enough bytes available */ if ((*(p_read + 1) == 0)||(*(p_read + 2) == 0)) { if (dcfg->check_encoding) { *error_msg = apr_psprintf(r->pool, "Invalid URL encoding detected: not enough characters"); return NULL; } else c = 0; } else { /* move onto the first hexadecimal letter */ p_read++; c = *p_read; if (dcfg->check_encoding) { if (! (((c >= '0')&&(c <= '9')) || ((c >= 'a')&&(c <= 'f')) || ((c >= 'A')&&(c <= 'F'))) ) { *error_msg = apr_psprintf(r->pool, "Invalid URL encoding detected: invalid characters used"); return NULL; } } c = *(p_read + 1); if (dcfg->check_encoding) { if (! (((c >= '0')&&(c <= '9')) || ((c >= 'a')&&(c <= 'f')) || ((c >= 'A')&&(c <= 'F'))) ) { *error_msg = apr_psprintf(r->pool, "Invalid URL encoding detected: invalid characters used"); return NULL; } } /* decode two hexadecimal letters into a single byte */ c = x2c(p_read); p_read++; } } if ((c < dcfg->range_start)||(c > dcfg->range_end)) { *error_msg = apr_psprintf(r->pool, "Invalid character detected [%i]", c); return NULL; } /* we replace null bytes with whitespace */ if (c == 0) c = 32; *p_write++ = c; p_read++; } *p_write = 0; return uri; } char *normalise_other_inplace(request_rec *r, sec_dir_config *dcfg, char *uri, char **error_msg) { unsigned char *p_read, *p_write, *p_slash; unsigned char c; int count; if (error_msg == NULL) return NULL; *error_msg = NULL; if (uri == NULL) return NULL; p_read = (unsigned char *)uri; p_write = (unsigned char *)uri; p_slash = NULL; count = 0; while (*p_read != 0) { c = *p_read; if (dcfg->check_unicode_encoding) { int urc = detect_unicode_character(r, (unsigned char *)p_read); switch(urc) { case UNICODE_ERROR_CHARACTERS_MISSING : *error_msg = apr_psprintf(r->pool, "Invalid Unicode encoding: not enough bytes"); return NULL; break; case UNICODE_ERROR_INVALID_ENCODING : *error_msg = apr_psprintf(r->pool, "Invalid Unicode encoding: invalid byte value"); return NULL; break; case UNICODE_ERROR_OVERLONG_CHARACTER : *error_msg = apr_psprintf(r->pool, "Invalid Unicode encoding: overlong character"); return NULL; break; } } switch (c) { #ifdef WIN32 case '\\' : #endif case '/' : if (p_slash == NULL) { /* remove the occurencies of "./" */ if ( (count > 1) && ((*(p_write - 1) == '.') && (*(p_write - 2) == '/')) ) { count -= 2; p_write -= 2; } p_slash = p_read; *p_write++ = '/'; p_read++; count++; } else { /* the previous character was a slash, we * will ignore this one - just increment * the read pointer */ p_read++; } break; default: /* p_slash is used to detect more than one * slash character in a row */ p_slash = NULL; *p_write++ = c; p_read++; count++; break; } } *p_write = 0; return uri; } char *normalise_inplace(request_rec *r, sec_dir_config *dcfg, char *uri, char **error_msg) { if (error_msg == NULL) return NULL; *error_msg = NULL; if (uri == NULL) { *error_msg = apr_psprintf(r->pool, "null given as argument"); return NULL; } if (normalise_urlencoding_inplace(r, dcfg, uri, error_msg) == NULL) { /* error_msg already populated */ return NULL; } if (normalise_other_inplace(r, dcfg, uri, error_msg) == NULL) { /* error_msg already populated */ return NULL; } return filter_multibyte_inplace(dcfg->charset_id, (char)dcfg->multibyte_replacement_byte, uri); } char *normalise_relaxed_inplace(request_rec *r, sec_dir_config *dcfg, char *uri, char **error_msg) { if (error_msg == NULL) return NULL; *error_msg = NULL; if (uri == NULL) { *error_msg = apr_psprintf(r->pool, "null given as argument"); return NULL; } if (normalise_urlencoding_relaxed_inplace(r, dcfg, uri, error_msg) == NULL) { /* error_msg already populated */ return NULL; } if (normalise_other_inplace(r, dcfg, uri, error_msg) == NULL) { /* error_msg already populated */ return NULL; } return filter_multibyte_inplace(dcfg->charset_id, (char)dcfg->multibyte_replacement_byte, uri); } char *normalise_relaxed(request_rec *r, sec_dir_config *dcfg, char *_uri, char **error_msg) { char *uri; if (error_msg == NULL) return NULL; *error_msg = NULL; if (_uri == NULL) { *error_msg = apr_psprintf(r->pool, "null given as argument"); return NULL; } uri = apr_pstrdup(r->pool, _uri); if (uri == NULL) return NULL; return normalise_relaxed_inplace(r, dcfg, uri, error_msg); } char *normalise(request_rec *r, sec_dir_config *dcfg, char *_uri, char **error_msg) { char *uri; if (_uri == NULL) return NULL; uri = apr_pstrdup(r->pool, _uri); if (uri == NULL) return NULL; return normalise_inplace(r, dcfg, uri, error_msg); } /** * Stores the per-request mod_security context in a place * (r->notes) where it will be found by other parts of * the code later. */ void store_msr(request_rec *r, modsec_rec *msr) { apr_table_setn(r->notes, NOTE_MSR, (char *)msr); sec_debug_log(r, 9, "Stored msr (%x) in r (%x)", msr, r); } /** * Looks for the mod_security context for the * current request. */ modsec_rec *find_msr(request_rec *r) { modsec_rec *msr = NULL; request_rec *rx; msr = (modsec_rec *)apr_table_get(r->notes, NOTE_MSR); if (msr != NULL) { sec_debug_log(r, 9, "Found msr (%x) in r (%x)", msr, 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) { sec_debug_log(r, 9, "Found msr (%x) in r->main (%x)", msr, r->main); 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) { sec_debug_log(r, 9, "Found msr (%x) in r->prev (%x)", msr, rx); return msr; } rx = rx->prev; } return NULL; } #define CHUNK_CAPACITY 8192 static int read_post_payload(modsec_rec *msr) { apr_pool_t *mptmp = NULL; apr_array_header_t *chunks_array = NULL; data_chunk *current_chunk = NULL; request_rec *r = msr->r; char *content_type, *content_length, *temps; char *my_error_msg = NULL; long len; msr->_post_payload = NULL; msr->_post_len = 0; if (msr->should_body_exist == 0) { sec_debug_log(r, 4, "read_post_payload: this request has no body (%i)", msr->should_body_exist); return 0; } if (msr->dcfg->scan_post != 1) { sec_debug_log(r, 4, "read_post_payload: request body buffering is off here (scan post = %i)", msr->dcfg->scan_post); return 0; } temps = (char *)get_env_var(r, "MODSEC_NOPOSTBUFFERING"); if (temps != NULL) { sec_debug_log(r, 2, "read_post_payload: POST scanning turned off dynamically (MODSEC_NOPOSTBUFFERING=\"%s\")", log_escape(r->pool, temps)); msr->post_payload_dynamic_off = 1; return 0; } /* figure out Content-Length */ content_length = (char *)apr_table_get(r->headers_in, "Content-Length"); if (content_length == NULL) { sec_debug_log(r, 2, "read_post_payload: Content-Length not found - unable to observe request body"); return 0; } len = strtol(content_length, NULL, 10); if ((len < 0)||(len + 1 <= 0)) { msr->tmp_message = apr_psprintf(r->pool, "Invalid Content-Length: %li", len); return -1; } /* msr->_post_len is unsigned long int */ msr->_post_len = len; /* test for the boundary case */ if (msr->_post_len + 1 == 0) { msr->tmp_message = apr_psprintf(r->pool, "Invalid Content-Length [%lu]", msr->_post_len); return -1; } /* Refuse to work with requests that are too large. Might prevent misconfiguration problems. */ if (msr->_post_len >= 1073741824) { msr->tmp_message = apr_psprintf(r->pool, "Content-Length too long for request buffering: %lu", msr->_post_len); return -1; } { sec_filter_in_ctx *ctx = NULL; apr_bucket_brigade *bb; int seen_eos = 0; apr_status_t rv; ctx = apr_pcalloc(r->pool, sizeof(*ctx)); if (ctx == NULL) { msr->_post_payload = NULL; msr->tmp_message = apr_psprintf(r->pool, "Unable to allocate %i bytes", sizeof(*ctx)); return -1; } msr->ctx_in = ctx; ctx->type = POST_IN_MEMORY; ctx->tmp_file_fd = -1; ctx->is_multipart = 0; ctx->buflen = msr->_post_len; ctx->sofar = 0; ctx->done_reading = 0; ctx->done_writing = 0; ctx->output_sent = 0; ctx->access_check_performed = 0; /* PUT request bodies should always be stored on the disk, * because they are files, and we need to look at them, * optionally store them, etc. */ if (r->method_number == M_PUT) { ctx->type = POST_ON_DISK; ctx->is_put = 1; } /* figure out the content-type */ content_type = (char *)apr_table_get(r->headers_in, "Content-Type"); /* * On POST requests, if the encoding is multipart/form-data and if * the size of the upload is greater than the maximum allowed * size, redirect the payload to a temporary file on disk. */ if ((content_type != NULL) &&(r->method_number == M_POST) &&(strncasecmp(content_type, "multipart/form-data", 19) == 0)) { ctx->is_multipart = TRUE; if (msr->_post_len > (unsigned int)msr->dcfg->upload_in_memory_limit) ctx->type = POST_ON_DISK; } /* initialize multipart handling */ if (ctx->is_multipart) { msr->mpd = (multipart_data *)apr_pcalloc(r->pool, sizeof(*(msr->mpd))); if (msr->mpd == NULL) { msr->_post_payload = NULL; msr->tmp_message = apr_psprintf(r->pool, "Unable to allocate %i bytes", sizeof(*(msr->mpd))); return -1; } if (multipart_init(msr->mpd, msr, &my_error_msg) < 0) { msr->_post_payload = NULL; msr->tmp_message = apr_psprintf(r->pool, "Failed to initialise multipart/form-data parsing: %s", my_error_msg); return -1; } } if (ctx->type == POST_IN_MEMORY) { apr_pool_create(&mptmp, NULL); if (mptmp == NULL) return -1; ctx->sofar = 0; chunks_array = apr_array_make(mptmp, 64, sizeof(data_chunk *)); if (chunks_array == NULL) return -1; current_chunk = (data_chunk *)apr_pcalloc(mptmp, sizeof(data_chunk)); if (current_chunk == NULL) return -1; current_chunk->data = malloc(CHUNK_CAPACITY); if (current_chunk->data == NULL) return -1; current_chunk->length = 0; *(data_chunk **)apr_array_push(chunks_array) = current_chunk; } else { char *folder = NULL; ctx->bufleft = ctx->buflen; if (msr->dcfg->upload_dir != NULL) folder = msr->dcfg->upload_dir; else folder = get_temp_folder(r->pool); ctx->tmp_file_name = apr_psprintf(r->pool, "%s/%s-%s-request_body-XXXXXX", folder, current_filetime(r), r->connection->remote_ip); if (ctx->tmp_file_name == NULL) { msr->_post_payload = NULL; sec_debug_log(r, 1, "read_post_payload: Memory allocation failed"); return -1; } ctx->tmp_file_fd = sec_mkstemp(ctx->tmp_file_name); if (ctx->tmp_file_fd < 0) { msr->_post_payload = NULL; msr->tmp_message = apr_psprintf(r->pool, "read_post_payload: Failed to create file \"%s\" because %d(\"%s\")", log_escape(r->pool, ctx->tmp_file_name), errno, log_escape(r->pool, strerror(errno))); return -1; } /* schedule resource cleanup for later */ apr_pool_cleanup_register(r->pool, (void *)msr, request_body_file_cleanup, apr_pool_cleanup_null); } bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); do { apr_bucket *bucket = NULL; rv = ap_get_brigade(r->input_filters, bb, AP_MODE_READBYTES, APR_BLOCK_READ, HUGE_STRING_LEN); if (rv != APR_SUCCESS) { msr->_post_payload = NULL; msr->tmp_message = apr_psprintf(r->pool, "Error reading request body, error code %i: %s", rv, get_apr_error(r->pool, rv)); goto RP_CLEAN_ERROR_RETURN; } while(!APR_BRIGADE_EMPTY(bb)) { const char *data; apr_size_t len; bucket = APR_BRIGADE_FIRST(bb); if (APR_BUCKET_IS_EOS(bucket)) { seen_eos = 1; } else if (APR_BUCKET_IS_FLUSH(bucket)) { /* do nothing */ } else { rv = apr_bucket_read(bucket, &data, &len, APR_BLOCK_READ); if (rv != APR_SUCCESS) { msr->_post_payload = NULL; msr->tmp_message = apr_psprintf(r->pool, "Error reading from a bucket, error code %i: %s", rv, get_apr_error(r->pool, rv)); goto RP_CLEAN_ERROR_RETURN; } sec_debug_log(r, 5, "read_post_payload: read %lu bytes", len); if (ctx->is_multipart) { char *my_error_msg = NULL; if (multipart_process_chunk(msr->mpd, data, len, &my_error_msg) < 0) { msr->_post_payload = NULL; msr->tmp_message = apr_psprintf(r->pool, "Error processing request body: %s", my_error_msg); goto RP_CLEAN_ERROR_RETURN; } } if (ctx->type == POST_IN_MEMORY) { long int bucket_offset, bucket_left; bucket_offset = 0; bucket_left = len; while(bucket_left > 0) { if (bucket_left < (CHUNK_CAPACITY - current_chunk->length)) { /* There's enough space in the current chunk. */ memcpy(current_chunk->data + current_chunk->length, data + bucket_offset, bucket_left); current_chunk->length += bucket_left; bucket_left = 0; } else { /* Fill the existing chunk. */ long int copy_length = CHUNK_CAPACITY - current_chunk->length; memcpy(current_chunk->data + current_chunk->length, data + bucket_offset, copy_length); bucket_offset += copy_length; bucket_left -= copy_length; current_chunk->length += copy_length; /* Allocate a new chunk. */ current_chunk = (data_chunk *)apr_pcalloc(mptmp, sizeof(data_chunk)); if (current_chunk == NULL) goto RP_CLEAN_ERROR_RETURN; current_chunk->data = malloc(CHUNK_CAPACITY); if (current_chunk->data == NULL) goto RP_CLEAN_ERROR_RETURN; current_chunk->length = 0; *(data_chunk **)apr_array_push(chunks_array) = current_chunk; } } ctx->sofar += len; } if (ctx->type == POST_ON_DISK) { int i; ctx->sofar += len; i = write(ctx->tmp_file_fd, data, len); if (i != len) { msr->_post_payload = NULL; msr->tmp_message = apr_psprintf(r->pool, "Error writing request body to file: %i", i); if (mptmp != NULL) apr_pool_destroy(mptmp); return -1; } } apr_bucket_delete(bucket); } if (seen_eos) break; } apr_brigade_cleanup(bb); } while(!seen_eos); ctx->done_reading = 1; if (ctx->is_multipart) { if (multipart_complete(msr->mpd, &my_error_msg) < 0) { msr->tmp_message = apr_psprintf(r->pool, "Error processing request body: %s", my_error_msg); goto RP_CLEAN_ERROR_RETURN; } } if (ctx->type == POST_ON_DISK) { if ((ctx->tmp_file_fd != 0)&&(ctx->tmp_file_fd != -1)) { close(ctx->tmp_file_fd); ctx->tmp_file_fd = -1; } } if (ctx->type == POST_IN_MEMORY) { data_chunk **chunks = NULL; int i; /* Allocate the required amount of memory first. */ ctx->buffer = apr_palloc(r->pool, ctx->sofar + 1); if ((ctx->buffer == NULL)||(ctx->sofar + 1 == 0)) { msr->_post_payload = NULL; msr->tmp_message = apr_psprintf(r->pool, "Failed to allocate %lu bytes", ctx->sofar + 1); goto RP_CLEAN_ERROR_RETURN; } /* Combine the data into a single buffer. */ ctx->buflen = 0; chunks = (data_chunk **)chunks_array->elts; for(i = 0; i < chunks_array->nelts; i++) { if (ctx->buflen + chunks[i]->length <= ctx->sofar) { memcpy(ctx->buffer + ctx->buflen, chunks[i]->data, chunks[i]->length); ctx->buflen += chunks[i]->length; } free(chunks[i]->data); chunks[i]->data = NULL; } ctx->buffer[ctx->buflen] = '\0'; ctx->output_ptr = ctx->buffer; apr_pool_destroy(mptmp); mptmp = NULL; } msr->is_body_read = 1; ap_add_input_filter_handle(global_sec_filter_in, ctx, r, r->connection); /* this is OK in all cases, ctx->buffer will * be NULL if the payload is not in memory */ msr->_post_payload = ctx->buffer; msr->_post_len = ctx->buflen; } return 1; RP_CLEAN_ERROR_RETURN: if (chunks_array != NULL) { data_chunk **chunks = NULL; int i; chunks = (data_chunk **)chunks_array->elts; for(i = 0; i < chunks_array->nelts; i++) { if (chunks[i]->data != NULL) { free((void *)chunks[i]->data); } } } if (mptmp != NULL) apr_pool_destroy(mptmp); return -1; } int parse_arguments(char *s, apr_table_t *parsed_args, request_rec *r, sec_dir_config *dcfg, char **error_msg) { long inputlength, i, j; char *my_error_msg = NULL; char *value = NULL; char *buf; int status; if (error_msg == NULL) return -1; *error_msg = NULL; if (s == NULL) return -1; inputlength = strlen(s); if (inputlength == 0) return 1; if (inputlength + 1 <= 0) return -1; buf = (char *)malloc(inputlength + 1); if (buf == NULL) { *error_msg = apr_psprintf(r->pool, "Failed to allocate %li bytes", inputlength + 1); return -1; } i = 0; j = 0; status = 0; while (i < inputlength) { if (status == 0) { /* parameter name */ while ((s[i] != '=') && (s[i] != '&') && (i < inputlength)) { buf[j] = s[i]; j++; i++; } buf[j++] = 0; } else { /* parameter value */ while ((s[i] != '&') && (i < inputlength)) { buf[j] = s[i]; j++; i++; } buf[j++] = 0; } if (status == 0) { if (normalise_inplace(r, dcfg, buf, &my_error_msg) == NULL) { free(buf); *error_msg = apr_psprintf(r->pool, "Error normalising parameter name: %s", my_error_msg); return -1; } if (s[i] == '&') { /* Empty parameter */ sec_debug_log(r, 4, "Adding parameter: \"%s\" (empty)", log_escape(r->pool, buf)); apr_table_add(parsed_args, buf, ""); status = 0; /* unchanged */ j = 0; } else { status = 1; value = &buf[j]; } } else { if (normalise_inplace(r, dcfg, value, &my_error_msg) == NULL) { free(buf); *error_msg = apr_psprintf(r->pool, "Error normalising parameter value: %s", my_error_msg); return -1; } sec_debug_log(r, 4, "Adding parameter: \"%s\"=\"%s\"", log_escape(r->pool, buf), log_escape(r->pool, value)); apr_table_add(parsed_args, buf, value); status = 0; j = 0; } i++; /* skip over the separator */ } /* last parameter was empty */ if (status == 1) { sec_debug_log(r, 4, "Adding parameter: \"%s\" (empty)", log_escape(r->pool, buf)); apr_table_add(parsed_args, buf, ""); } free(buf); return 1; } char *remove_binary_content(request_rec *r, char *data, long size) { char *src, *dst, *newdata; if (data == NULL) return NULL; if ((size < 0)||(size + 1 <= 0)) return NULL; /* make a copy of the payload first */ newdata = apr_palloc(r->pool, size + 1); if (newdata == NULL) { sec_debug_log(r, 1, "remove_binary_content: failed to allocate %li bytes", size + 1); return NULL; } /* remove zeros from the payload */ src = data; dst = newdata; while(size--) { if (*src != 0) *dst++ = *src++; else src++; } *dst = 0; return newdata; } int check_single_signature(modsec_rec *msr, signature *sig) { char *my_error_msg; int rc = _check_single_signature(msr, sig, &my_error_msg); if (rc == DECLINED) { msr->tmp_message = apr_psprintf(msr->r->pool, "Error processing signature: %s", my_error_msg); return perform_action(msr, msr->dcfg->actionset, sig); } return rc; } int _check_single_signature(modsec_rec *msr, signature *sig, char **error_msg) { int j, rs; if (error_msg == NULL) return HTTP_INTERNAL_SERVER_ERROR; *error_msg = NULL; /* * the idea behind non-selective filters is to apply them over * raw data, typically the complete first line of the request * and the complete POST payload */ if (sig->is_selective == 0) { sec_debug_log(msr->r, 2, "Checking signature \"%s\" at REQUEST_URI", log_escape(msr->r->pool, sig->pattern)); rs = check_sig_against_string(msr, sig, msr->request_uri, VAR_REQUEST_URI, NULL); if (rs != OK) return rs; if (msr->is_body_read) { if (msr->mpd != NULL) { /* multipart/form-data request */ if (msr->_fake_post_payload == NULL) { msr->_fake_post_payload = construct_fake_urlencoded(msr, msr->parsed_args); if (msr->_fake_post_payload == NULL) { *error_msg = apr_psprintf(msr->r->pool, "Failed during fake POST payload construction"); return DECLINED; } } sec_debug_log(msr->r, 2, "Checking signature \"%s\" at POST_PAYLOAD (faked)", log_escape(msr->r->pool, sig->pattern)); rs = check_sig_against_string(msr, sig, msr->_fake_post_payload, VAR_POST_PAYLOAD, NULL); if (rs != OK) return rs; } else { if (msr->_post_payload != NULL) { sec_debug_log(msr->r, 2, "Checking signature \"%s\" at POST_PAYLOAD", log_escape(msr->r->pool, sig->pattern)); rs = check_sig_against_string(msr, sig, msr->_post_payload, VAR_POST_PAYLOAD, NULL); if (rs != OK) return rs; } } } } else { variable **variables; const char *v; /* this is a selective signature, this means that we need to * check only one part of the request and leave the rest alone */ /* selective signatures can be negative and non-negative; * non-negative signatures consist of a list of variables * that represent parts of the request that need to be * checked; negative signatures apply only to request * arguments, when you want to exclude an argument from * a check */ if (sig->is_negative == 0) { /* loop through signature variables and * check them */ variables = (variable **)sig->variables->elts; for (j = 0; j < sig->variables->nelts; j++) { if (variables[j]->type == VAR_ARGS) { sec_debug_log(msr->r, 2, "Checking signature \"%s\" at QUERY_STRING", log_escape(msr->r->pool, sig->pattern)); v = get_variable(msr, variables[j], VAR_QUERY_STRING); rs = check_sig_against_string(msr, sig, v, VAR_QUERY_STRING, NULL); if (rs != OK) return rs; if (msr->is_body_read) { if (msr->mpd != NULL) { /* multipart/form-data request */ if (msr->_fake_post_payload == NULL) { msr->_fake_post_payload = construct_fake_urlencoded(msr, msr->parsed_args); if (msr->_fake_post_payload == NULL) { *error_msg = apr_psprintf(msr->r->pool, "Failed during fake POST payload construction"); return DECLINED; } } sec_debug_log(msr->r, 2, "Checking signature \"%s\" at POST_PAYLOAD (faked)", log_escape(msr->r->pool, sig->pattern)); rs = check_sig_against_string(msr, sig, msr->_fake_post_payload, VAR_POST_PAYLOAD, NULL); if (rs != OK) return rs; } else { if (msr->_post_payload != NULL) { sec_debug_log(msr->r, 2, "Checking signature \"%s\" at POST_PAYLOAD", log_escape(msr->r->pool, sig->pattern)); rs = check_sig_against_string(msr, sig, msr->_post_payload, VAR_POST_PAYLOAD, NULL); if (rs != OK) return rs; } } } } else if (variables[j]->type == VAR_POST_PAYLOAD) { /* Ignore requests without bodies */ if (msr->should_body_exist) { /* Note it can happen that a body is available but * _post_payload is NULL. */ if (msr->is_body_read == 0) { /* Only complain if body is not available by configuration mistake */ if (msr->post_payload_dynamic_off == 0) { sec_debug_log(msr->r, 1, "Filtering against POST payload requested but payload is not available"); } return OK; } else { if (msr->mpd == NULL) { if (msr->_post_payload != NULL) { sec_debug_log(msr->r, 2, "Checking signature \"%s\" at POST_PAYLOAD", log_escape(msr->r->pool, sig->pattern)); rs = check_sig_against_string(msr, sig, msr->_post_payload, VAR_POST_PAYLOAD, NULL); if (rs != OK) return rs; } } else { /* multipart/form-data request */ if (msr->_fake_post_payload == NULL) { msr->_fake_post_payload = construct_fake_urlencoded(msr, msr->parsed_args); if (msr->_fake_post_payload == NULL) { *error_msg = apr_psprintf(msr->r->pool, "Failed during fake POST payload construction"); return DECLINED; } } sec_debug_log(msr->r, 2, "Checking signature \"%s\" at POST_PAYLOAD (faked)", log_escape(msr->r->pool, sig->pattern)); rs = check_sig_against_string(msr, sig, msr->_fake_post_payload, VAR_POST_PAYLOAD, NULL); if (rs != OK) return rs; } } } } else if (variables[j]->type == VAR_ARGS_NAMES) { apr_table_entry_t *te; const apr_array_header_t *arr; int k; sec_debug_log(msr->r, 2, "Checking signature \"%s\" at ARGS_NAMES", log_escape(msr->r->pool, sig->pattern)); arr = apr_table_elts(msr->parsed_args); te = (apr_table_entry_t *)arr->elts; for (k = 0; k < arr->nelts; k++) { rs = check_sig_against_string(msr, sig, te[k].key, VAR_ARGS_NAMES, NULL); if (rs != OK) return rs; } } else if (variables[j]->type == VAR_ARGS_VALUES) { apr_table_entry_t *te; const apr_array_header_t *arr; int k; sec_debug_log(msr->r, 2, "Checking signature \"%s\" at ARGS_VALUES", log_escape(msr->r->pool, sig->pattern)); arr = apr_table_elts(msr->parsed_args); te = (apr_table_entry_t *)arr->elts; for (k = 0; k < arr->nelts; k++) { sec_debug_log(msr->r, 4, "Checking signature \"%s\" at ARGS_VALUES(\"%s\")", log_escape(msr->r->pool, sig->pattern), log_escape(msr->r->pool, te[k].key)); rs = check_sig_against_string(msr, sig, te[k].val, VAR_ARGS_VALUES, te[k].key); if (rs != OK) return rs; } } else if (variables[j]->type == VAR_COOKIES_NAMES) { apr_table_entry_t *te; const apr_array_header_t *arr; int k; sec_debug_log(msr->r, 2, "Checking signature \"%s\" at COOKIES_NAMES", log_escape(msr->r->pool, sig->pattern)); arr = apr_table_elts(msr->parsed_cookies); te = (apr_table_entry_t *)arr->elts; for (k = 0; k < arr->nelts; k++) { /*sec_debug_log(msr->r, 5, "Cookie \"%s\"="%s\"", log_escape(msr->r->pool, te[k].key), log_escape(msr->r->pool, te[k].val));*/ rs = check_sig_against_string(msr, sig, te[k].key, VAR_COOKIES_NAMES, NULL); if (rs != OK) return rs; } } else if (variables[j]->type == VAR_COOKIES_VALUES) { apr_table_entry_t *te; const apr_array_header_t *arr; int k; sec_debug_log(msr->r, 2, "Checking signature \"%s\" at COOKIES_VALUES", log_escape(msr->r->pool, sig->pattern)); arr = apr_table_elts(msr->parsed_cookies); te = (apr_table_entry_t *)arr->elts; for (k = 0; k < arr->nelts; k++) { sec_debug_log(msr->r, 4, "Checking signature \"%s\" at COOKIES_VALUES(\"%s\")", log_escape(msr->r->pool, sig->pattern), log_escape(msr->r->pool, te[k].key)); rs = check_sig_against_string(msr, sig, te[k].val, VAR_COOKIES_VALUES, te[k].key); if (rs != OK) return rs; } } else if (variables[j]->type == VAR_HEADERS) { apr_table_entry_t *te; const apr_array_header_t *arr; int k; sec_debug_log(msr->r, 2, "Checking signature \"%s\" at HEADERS", log_escape(msr->r->pool, sig->pattern)); arr = apr_table_elts(msr->cache_headers_in); te = (apr_table_entry_t *)arr->elts; for (k = 0; k < arr->nelts; k++) { char *header_value = apr_psprintf(msr->r->pool, "%s: %s", te[k].key, te[k].val); rs = check_sig_against_string(msr, sig, header_value, VAR_HEADERS, NULL); if (rs != OK) return rs; } } else if (variables[j]->type == VAR_HEADERS_NAMES) { apr_table_entry_t *te; const apr_array_header_t *arr; int k; sec_debug_log(msr->r, 2, "Checking signature \"%s\" at HEADERS_NAMES", log_escape(msr->r->pool, sig->pattern)); arr = apr_table_elts(msr->cache_headers_in); te = (apr_table_entry_t *)arr->elts; for (k = 0; k < arr->nelts; k++) { rs = check_sig_against_string(msr, sig, te[k].key, VAR_HEADERS_NAMES, NULL); if (rs != OK) return rs; } } else if (variables[j]->type == VAR_HEADERS_VALUES) { apr_table_entry_t *te; const apr_array_header_t *arr; int k; sec_debug_log(msr->r, 2, "Checking signature \"%s\" at HEADERS_VALUES", log_escape(msr->r->pool, sig->pattern)); arr = apr_table_elts(msr->cache_headers_in); te = (apr_table_entry_t *)arr->elts; for (k = 0; k < arr->nelts; k++) { sec_debug_log(msr->r, 4, "Checking signature \"%s\" at HEADERS_VALUES(\"%s\")", log_escape(msr->r->pool, sig->pattern), log_escape(msr->r->pool, te[k].key)); rs = check_sig_against_string(msr, sig, te[k].val, VAR_HEADERS_VALUES, te[k].key); if (rs != OK) return rs; } } else if (variables[j]->type == VAR_FILES_NAMES) { sec_debug_log(msr->r, 2, "Checking signature \"%s\" at FILES_NAMES", log_escape(msr->r->pool, sig->pattern)); if (msr->mpd != NULL) { rs = multipart_check_files_names(msr, sig, variables[j]); if (rs != OK) return rs; } } else if (variables[j]->type == VAR_FILES_SIZES) { sec_debug_log(msr->r, 2, "Checking signature \"%s\" at FILES_SIZES", log_escape(msr->r->pool, sig->pattern)); if (msr->mpd != NULL) { rs = multipart_check_files_sizes(msr, sig, variables[j]); if (rs != OK) return rs; } } else if (variables[j]->type == VAR_COOKIE) { apr_table_entry_t *te; const apr_array_header_t *arr; int k, count = 0; count = 0; arr = apr_table_elts(msr->parsed_cookies); te = (apr_table_entry_t *)arr->elts; for (k = 0; k < arr->nelts; k++) { if (strcasecmp(te[k].key, variables[j]->name) == 0) { count++; sec_debug_log(msr->r, 2, "Checking signature \"%s\" at COOKIE(\"%s\")", log_escape(msr->r->pool, sig->pattern), variables[j]->name); rs = check_sig_against_string(msr, sig, te[k].val, VAR_COOKIE, te[k].key); if (rs != OK) return rs; } } /* If the named cookie does not exist make a check * treating it as it were present but empty. */ if (count == 0) { sec_debug_log(msr->r, 2, "Checking signature \"%s\" at COOKIE(\"%s\")", log_escape(msr->r->pool, sig->pattern), variables[j]->name); rs = check_sig_against_string(msr, sig, "", VAR_COOKIE, variables[j]->name); if (rs != OK) return rs; } } else if (variables[j]->type == VAR_ARG) { apr_table_entry_t *te; const apr_array_header_t *arr; int k, count = 0; count = 0; arr = apr_table_elts(msr->parsed_args); te = (apr_table_entry_t *)arr->elts; for (k = 0; k < arr->nelts; k++) { if (strcasecmp(te[k].key, variables[j]->name) == 0) { count++; sec_debug_log(msr->r, 2, "Checking signature \"%s\" at ARG(\"%s\")", log_escape(msr->r->pool, sig->pattern), variables[j]->name); rs = check_sig_against_string(msr, sig, te[k].val, VAR_ARG, te[k].key); if (rs != OK) return rs; } } /* If the named parameter does not exist make a check * treating it as it were present but empty */ if (count == 0) { sec_debug_log(msr->r, 2, "Checking signature \"%s\" at ARG(\"%s\")", log_escape(msr->r->pool, sig->pattern), variables[j]->name); rs = check_sig_against_string(msr, sig, "", VAR_ARG, variables[j]->name); if (rs != OK) return rs; } } else { /* simple variable, get the value and check it */ char *where = NULL; if (variables[j]->name == NULL) where = apr_psprintf(msr->r->pool, "%s", all_variables[variables[j]->type]); else where = apr_psprintf(msr->r->pool, "%s(%s)", all_variables[variables[j]->type], variables[j]->name); v = get_variable(msr, variables[j], variables[j]->type); if (v != NULL) { sec_debug_log(msr->r, 2, "Checking signature \"%s\" at %s", log_escape(msr->r->pool, sig->pattern), where); rs = check_sig_against_string(msr, sig, (char *)v, variables[j]->type, variables[j]->name); if (rs != OK) return rs; } else { sec_debug_log(msr->r, 1, "Variable not found \"%s\"", log_escape(msr->r->pool, where)); } } } } else { apr_table_t *our_parsed_args; char *fake_body = NULL; our_parsed_args = apr_table_copy(msr->r->pool, msr->parsed_args); /* Find the unwanted variable names in the signature * data and remove them from the variable list. */ variables = (variable **)sig->variables->elts; for (j = 0; j < sig->variables->nelts; j++) { if ((variables[j]->type == VAR_ARG) && (variables[j]->action == VAR_ACTION_ALLOW)) { apr_table_unset(our_parsed_args, variables[j]->name); } } fake_body = construct_fake_urlencoded(msr, our_parsed_args); if (fake_body == NULL) { *error_msg = apr_psprintf(msr->r->pool, "Failed with construct_fake_urlencoded"); return DECLINED; } /* make the check against the compiled string */ sec_debug_log(msr->r, 2, "Checking signature \"%s\" at ARGS_SELECTIVE", log_escape(msr->r->pool, sig->pattern)); rs = check_sig_against_string(msr, sig, (char *)fake_body, VAR_ARGS_SELECTIVE, NULL); if (rs != OK) return rs; } } return OK; } int sec_check_all_signatures(modsec_rec *msr) { request_rec *r = msr->r; signature **signatures; int i; int mode = 0; int skip_count = 0; int rc = DECLINED; /* loop through all signatures */ signatures = (signature **)msr->dcfg->signatures->elts; for (i = 0; i < msr->dcfg->signatures->nelts; i++) { /* do not process signatures that are not proper signatures */ if (signatures[i]->is_inheritance_placeholder != 0) continue; /* output rules are not processed here */ if (signatures[i]->is_output != PHASE_INPUT) continue; /* check if we need to skip this rule */ if (skip_count > 0) { skip_count--; continue; } /* just clear the flag, we had to use the flag * to detect a case when the last rule in the * rule chain was marked as chained */ if (mode == 2) mode = 0; /* in mode 1, we are looking for the next filter, * next chained that is, and then skip it to * execute the next filter in the chain */ if (mode == 1) { if ((signatures[i]->actionset == NULL) || ((signatures[i]->actionset != NULL)&&(signatures[i]->actionset->is_chained == 0)) ) { mode = 0; } continue; } msr->tmp_message = NULL; rc = check_single_signature(msr, signatures[i]); sec_debug_log(r, 9, "Signature check returned %i", rc); /* MODSEC_ALLOW means that an allow action was called, * we need to pass the request through */ if (rc == MODSEC_ALLOW) { sec_debug_log(r, 9, "Allow request to pass through"); return DECLINED; } /* OK means there was no filter match, we * switch to mode 1, processing will continue * with the next filter chain */ if (rc == OK) { /* we go into mode 1 (looking for the last rule * in the chain) if this rule is not it */ if ((signatures[i]->actionset != NULL)&&(signatures[i]->actionset->is_chained == 1)) { sec_debug_log(r, 9, "Chained rule and no match, find the next rule not in chain"); mode = 1; } continue; } /* any status greater than zero means there * was a filter match, so we either stop execution * or proceed to the next rule if this rule was * chained */ if (rc > 0) { if ((signatures[i]->actionset != NULL)&&(signatures[i]->actionset->is_chained == 1)) { mode = 2; sec_debug_log(r, 9, "Chained rule with match, continue in the loop"); continue; } else { rule_match: sec_debug_log(r, 9, "Rule match, returning code %i", rc); return rc; } } /* if the return status is zero this * means skip some rules, so we skip */ if (rc == MODSEC_SKIP) { if (signatures[i]->actionset == NULL) skip_count = 1; else skip_count = signatures[i]->actionset->skip_count; continue; } sec_debug_log(r, 1, "Unprocessed return code %i", rc); return DECLINED; } /* handle the case where there was a match on the * last filter so mode 2 check could not be done * strickly speaking this should be a configuration error */ if (mode == 2) { sec_debug_log(r, 1, "Last rule marked as chained - ignoring"); goto rule_match; } return DECLINED; } static int sec_exec_child(char *command, const char *argv[], request_rec *r, char **output) { apr_procattr_t *procattr = NULL; apr_proc_t *procnew = NULL; apr_status_t rc = APR_SUCCESS; const char *const *env = NULL; apr_file_t *script_out = NULL; char *exec_dir = "", *exec_command = command; if (argv == NULL) { argv = apr_pcalloc(r->pool, 3 * sizeof(char *)); argv[0] = command; argv[1] = NULL; } ap_add_cgi_vars(r); ap_add_common_vars(r); /* PHP hack, getting around its security checks */ apr_table_add(r->subprocess_env, "PATH_TRANSLATED", command); apr_table_add(r->subprocess_env, "REDIRECT_STATUS", "302"); env = (const char * const *)ap_create_environment(r->pool, r->subprocess_env); if (env == NULL) { sec_debug_log(r, 1, "sec_exec_child: Unable to create environment"); return DECLINED; } procnew = apr_pcalloc(r->pool, sizeof(*procnew)); if (procnew == NULL) { sec_debug_log(r, 1, "sec_exec_child: Unable to allocate %i bytes", sizeof(*procnew)); return DECLINED; } apr_procattr_create(&procattr, r->pool); if (procattr == NULL) { sec_debug_log(r, 1, "sec_exec_child: Unable to create procattr"); return DECLINED; } apr_procattr_io_set(procattr, APR_NO_PIPE, APR_FULL_BLOCK, APR_NO_PIPE); #if !(defined(WIN32) || defined(DISABLE_SUEXEC)) { char *p; /* Suexec will complain if the name of the * script starts with a slash. To work around * that we chdir to the folder, and then execute * the script giving a relative filename. We have * already forked so we can do that. */ exec_dir = apr_pstrdup(r->pool, command); p = strrchr(exec_dir, '/'); if (p != NULL) { exec_command = p + 1; *p = 0; chdir(exec_dir); } else { exec_command = command; exec_dir = ""; } } #else exec_command = command; exec_dir = ""; #endif #ifdef DISABLE_SUEXEC rc = apr_proc_create(procnew, exec_command, argv, env, procattr, r->pool); #else rc = ap_os_create_privileged_process(r, procnew, exec_command, argv, env, procattr, r->pool); #endif if (rc != APR_SUCCESS) { sec_debug_log(r, 1, "Failed to execute: \"%s\" (rc=%d)", log_escape(r->pool, command), rc); return rc; } apr_pool_note_subprocess(r->pool, procnew, APR_KILL_AFTER_TIMEOUT); script_out = procnew->out; if (!script_out) { sec_debug_log(r, 1, "sec_exec_chiled: Failed to get script output pipe"); return DECLINED; } apr_file_pipe_timeout_set(script_out, r->server->timeout); { char buf[260] = ""; char *p = buf; apr_size_t nbytes = 255; apr_status_t rc2; rc2 = apr_file_read(script_out, buf, &nbytes); if (rc2 == APR_SUCCESS) { buf[nbytes] = 0; /* if there is more than one line ignore them */ while(*p != 0) { if (*p == 0x0a) *p = 0; p++; } sec_debug_log(r, 4, "sec_exec_child: First line from script output: \"%s\"", log_escape(r->pool, buf)); if (output != NULL) *output = apr_pstrdup(r->pool, buf); /* soak up the remaining data */ nbytes = 255; while(apr_file_read(script_out, buf, &nbytes) == APR_SUCCESS) nbytes = 255; } else { sec_debug_log(r, 1, "File execution failed: %s (%s)", exec_command, get_apr_error(r->pool, rc2)); return DECLINED; } } apr_proc_wait(procnew, NULL, NULL, APR_WAIT); return rc; } int check_sig_against_string(modsec_rec *msr, signature *_sig, const char *s, int var_type, char *var_name) { request_rec *r = msr->r; apr_time_t time_before_regex; int regex_result = 0; int rc = OK; if (_sig->regex == NULL) { msr->tmp_message = apr_psprintf(r->pool, "Compiled regex for pattern \"%s\" is null!", log_escape(r->pool, _sig->pattern)); return perform_action(msr, msr->dcfg->actionset, _sig); } if (s == NULL) { msr->tmp_message = apr_psprintf(r->pool, "check_sig_against_sig: Internal Error: received null for argument"); return perform_action(msr, msr->dcfg->actionset, _sig);; } sec_debug_log(r, 4, "Checking against \"%s\"", log_escape(r->pool, (char *)s)); time_before_regex = apr_time_now(); regex_result = ap_regexec(_sig->regex, s, 0, NULL, 0); sec_debug_log(r, 9, "Check took %u usec", (apr_time_now() - time_before_regex)); if ( ((regex_result == 0)&&(_sig->is_allow == 0)) || ((regex_result != 0)&&(_sig->is_allow == 1)) ) { if (var_name == NULL) msr->tmp_message = apr_psprintf(msr->r->pool, "Pattern match \"%s\" at %s", log_escape(r->pool, _sig->pattern), all_variables[var_type]); else msr->tmp_message = apr_psprintf(msr->r->pool, "Pattern match \"%s\" at %s(\"%s\")", log_escape(r->pool, _sig->pattern), all_variables[var_type], log_escape(r->pool, var_name)); rc = perform_action(msr, msr->dcfg->actionset, _sig); } return rc; } char *process_action(char *name, char *value, actionset_t *actionset, apr_pool_t *_pool) { if ((value != NULL)&&(strlen(value) == 0)) value = NULL; if (strcmp(name, "log") == 0) { actionset->log = 1; } else if (strcmp(name, "nolog") == 0) { actionset->log = 0; } else if (strcmp(name, "auditlog") == 0) { actionset->auditlog = 1; } else if (strcmp(name, "noauditlog") == 0) { actionset->auditlog = 0; } else if (strcmp(name, "status") == 0) { actionset->action = ACTION_DENY; if (value != NULL) { actionset->status = atoi(value); } else { return apr_psprintf(_pool, "Action \"status\" requires a parameter"); } } else if ((strcmp(name, "chain") == 0)||(strcmp(name, "chained") == 0)) { actionset->is_chained = 1; } else if ((strcmp(name, "skipnext") == 0)||(strcmp(name, "skip") == 0)) { actionset->action = ACTION_SKIP; actionset->skip_count = 1; if (value != NULL) { actionset->skip_count = atoi(value); if (actionset->skip_count <= 0) return apr_psprintf(_pool, "Invalid value for action '%s': %s", name, value); } } else if (strcmp(name, "deny") == 0) { actionset->action = ACTION_DENY; } else if (strcmp(name, "allow") == 0) { actionset->action = ACTION_ALLOW; } else if (strcmp(name, "pass") == 0) { actionset->action = ACTION_NONE; } else if (strcmp(name, "exec") == 0) { actionset->exec = 1; if (value != NULL) { actionset->exec_string = apr_pstrdup(_pool, value); } else { return apr_psprintf(_pool, "Action \"exec\" requires a parameter"); } } else if (strcmp(name, "redirect") == 0) { actionset->action = ACTION_REDIRECT; if (value != NULL) { actionset->redirect_url = apr_pstrdup(_pool, value); } else { return apr_psprintf(_pool, "Action \"redirect\" requires a parameter"); } } else if (strcmp(name, "proxy") == 0) { actionset->action = ACTION_PROXY; if (value != NULL) { actionset->proxy_url = apr_pstrdup(_pool, value); } else { return apr_psprintf(_pool, "Action \"proxy\" requires a parameter"); } } else if (strcmp(name, "mandatory") == 0) { actionset->mandatory = 1; } else if (strcmp(name, "msg") == 0) { if (value != NULL) { actionset->msg = apr_pstrdup(_pool, value); } else { return apr_psprintf(_pool, "Action \"msg\" requires a parameter"); } } else if (strcmp(name, "id") == 0) { if (value != NULL) { actionset->id = apr_pstrdup(_pool, value); } else { return apr_psprintf(_pool, "Action \"id\" requires a parameter"); } } else if (strcmp(name, "rev") == 0) { if (value != NULL) { actionset->rev = apr_pstrdup(_pool, value); } else { return apr_psprintf(_pool, "Action \"rev\" requires a parameter"); } } else if (strcmp(name, "severity") == 0) { if (value != NULL) { actionset->severity = parse_severity(value); if (actionset->severity == -1) { return apr_psprintf(_pool, "Invalid value for severity: %s", value); } } else { return apr_psprintf(_pool, "Action \"severity\" requires a parameter"); } } else if (strcmp(name, "pause") == 0) { if (value != NULL) { actionset->pause = atoi(value); if (actionset->pause <= 0) { return apr_psprintf(_pool, "Invalid value for action 'pause': %s", value); } } else { return apr_psprintf(_pool, "Action \"pause\" requires a parameter"); } } else if (strcmp(name, "setenv") == 0) { if (value != NULL) { char *peq = NULL; actionset->env_name = apr_pstrdup(_pool, value); peq = strstr(actionset->env_name, "="); if (peq != NULL) { actionset->env_value = peq + 1; *peq = 0; /* terminate env_name */ } else { /* missing value, assume "1" */ actionset->env_value = "1"; } } else { return apr_psprintf(_pool, "Action \"setenv\" requires a parameter"); } } else if (strcmp(name, "setnote") == 0) { if (value != NULL) { char *peq = NULL; actionset->note_name = apr_pstrdup(_pool, value); peq = strstr(actionset->note_name, "="); if (peq != NULL) { actionset->note_value = peq + 1; *peq = 0; /* terminate note_name */ } else { /* missing value, assume "1" */ actionset->note_value = "1"; } } } else if (strcmp(name, "logparts") == 0) { if (value != NULL) { if (value[0] == '+') { actionset->logparts = RELATIVE_VALUE_POSITIVE; actionset->logparts_value = apr_pstrdup(_pool, value + 1); } else if (value[0] == '-') { actionset->logparts = RELATIVE_VALUE_NEGATIVE; actionset->logparts_value = apr_pstrdup(_pool, value + 1); } else { actionset->logparts = ABSOLUTE_VALUE; actionset->logparts_value = apr_pstrdup(_pool, value); } if (is_valid_parts_specification(actionset->logparts_value) != 1) { return apr_psprintf(_pool, "Invalid parts specification: %s", actionset->logparts_value); } } else { return apr_psprintf(_pool, "Action \"logparts\" requires a parameter"); } } else { return apr_psprintf(_pool, "Unknown action: %s", name); } return NULL; } char *parse_actionset(char *p2, actionset_t *actionset, apr_pool_t *_pool) { char *p, *t = apr_pstrdup(_pool, p2); char *name, *name_end, *value, *rc; p = t; while(*p != '\0') { name = NULL; value = NULL; /* ignore whitespace */ while(isspace(*p)) p++; if (*p == '\0') return NULL; /* we are at the beginning of a name */ name = p; while((*p != '\0')&&(*p != ':')&&(*p != ',')&&(!isspace(*p))) p++; name_end = p; while(isspace(*p)) p++; if (*p == '\0') { return process_action(name, value, actionset, _pool);; } if (*p == ',') { *name_end = '\0'; rc = process_action(name, value, actionset, _pool); if (rc != NULL) return rc; p++; continue; } if (*p != ':') { return apr_psprintf(_pool, "Invalid action list, colon or comma expected at position %i: %s", (int)(p - t), p2); } *name_end = '\0'; /* ignore whitespace after colon */ p++; while(isspace(*p)) p++; /* we'll allow empty values */ if (*p == '\0') { return process_action(name, value, actionset, _pool); } if (*p == ',') { rc = process_action(name, value, actionset, _pool); if (rc != NULL) return rc; continue; } if (*p == '\'') { /* quoted value */ char *d = NULL; p++; /* go over the openning quote */ value = d = p; for(;;) { if (*p == '\0') { return apr_psprintf(_pool, "Invalid action list, missing closing quote: %s", p2); } else if (*p == '\\') { if ( (*(p + 1) == '\0') || ((*(p + 1) != '\'')&&(*(p + 1) != '\\')) ) { return apr_psprintf(_pool, "Invalid quoting in the action list"); } p++; *d++ = *p++; } else if (*p == '\'') { *d = '\0'; p++; break; } else { *d++ = *p++; } } } else { /* non-quoted value */ value = p; while((*p != '\0')&&(*p != ',')&&(!isspace(*p))) p++; if (*p != '\0') { *p++ = '\0'; /* write over the comma or a space */ } } rc = process_action(name, value, actionset, _pool); if (rc != NULL) return rc; while(isspace(*p)||(*p == ',')) p++; } /* Chained rules must always try to deny * access in order for chaining to work * properly */ if (actionset->is_chained) { actionset->action = ACTION_DENY; actionset->status = HTTP_FORBIDDEN; } return NULL; } static const char *cmd_filter_check_encoding(cmd_parms *cmd, void *in_dcfg, int flag) { sec_dir_config *dcfg = in_dcfg; dcfg->check_encoding = flag; return NULL; } static const char *cmd_filter_check_unicode_encoding(cmd_parms *cmd, void *in_dcfg, int flag) { sec_dir_config *dcfg = in_dcfg; dcfg->check_unicode_encoding = flag; return NULL; } static const char *cmd_filter_force_byte_range(cmd_parms *cmd, void *in_dcfg, const char *p1, const char *p2) { sec_dir_config *dcfg = in_dcfg; dcfg->range_start = atoi(p1); dcfg->range_end = atoi(p2); if ((dcfg->range_start < 0)||(dcfg->range_end > 255)||(dcfg->range_start >= dcfg->range_end)) { return (const char *)apr_psprintf(cmd->pool, "Invalid range"); } return NULL; } static const char *cmd_filter_engine(cmd_parms *cmd, void *in_dcfg, const char *p1) { sec_dir_config *dcfg = in_dcfg; if (strcasecmp(p1, "On") == 0) dcfg->filter_engine = FILTERING_ON; else if (strcasecmp(p1, "Off") == 0) dcfg->filter_engine = FILTERING_OFF; else if (strcasecmp(p1, "DynamicOnly") == 0) dcfg->filter_engine = FILTERING_DYNAMIC_ONLY; else return (const char *)apr_psprintf(cmd->pool, "Unrecognized parameter value for SecFilterEngine: %s", p1); return NULL; } static const char *cmd_filter_inheritance(cmd_parms *cmd, void *in_dcfg, int flag) { sec_dir_config *dcfg = in_dcfg; if (flag) dcfg->filters_clear = 0; else dcfg->filters_clear = 1; return NULL; } static const char *cmd_server_response_token(cmd_parms *cmd, void *in_dcfg, int flag) { sec_srv_config *scfg = (sec_srv_config *)ap_get_module_config(cmd->server->module_config, &security_module); if (cmd->server->is_virtual) { return "SecServerResponseToken not allowed in VirtualHost"; } scfg->server_response_token = flag; return NULL; } static const char *cmd_audit_engine(cmd_parms *cmd, void *in_dcfg, const char *p1) { sec_dir_config *dcfg = in_dcfg; if (strcasecmp(p1, "On") == 0) dcfg->auditlog_flag = AUDITLOG_ON; else if (strcasecmp(p1, "Off") == 0) dcfg->auditlog_flag = AUDITLOG_OFF; else if (strcasecmp(p1, "RelevantOnly") == 0) dcfg->auditlog_flag = AUDITLOG_RELEVANT_ONLY; else if (strcasecmp(p1, "DynamicOrRelevant") == 0) dcfg->auditlog_flag = AUDITLOG_DYNAMIC_OR_RELEVANT; else return (const char *)apr_psprintf(cmd->pool, "Unrecognised parameter value for SecAuditEngine: %s", p1); return NULL; } static const char *cmd_audit_log(cmd_parms *cmd, void *in_dcfg, const char *p1) { sec_dir_config *dcfg = in_dcfg; dcfg->auditlog_name = (char *)p1; if (dcfg->auditlog_name[0] == '|') { const char *pipe_name = ap_server_root_relative(cmd->pool, dcfg->auditlog_name + 1); piped_log *pipe_log; pipe_log = ap_open_piped_log(cmd->pool, pipe_name); if (pipe_log == NULL) { return apr_psprintf(cmd->pool, "mod_security: Failed to open the audit log pipe: %s", pipe_name); } dcfg->auditlog_fd = ap_piped_log_write_fd(pipe_log); } else { const char *file_name = ap_server_root_relative(cmd->pool, dcfg->auditlog_name); apr_status_t rc; rc = apr_file_open(&dcfg->auditlog_fd, file_name, APR_WRITE | APR_APPEND | APR_CREATE | APR_BINARY, CREATEMODE, cmd->pool); if (rc != APR_SUCCESS) { return apr_psprintf(cmd->pool, "mod_security: Failed to open the audit log file: %s", file_name); } } return NULL; } static const char *cmd_audit_log_type(cmd_parms *cmd, void *in_dcfg, const char *p1) { sec_dir_config *dcfg = in_dcfg; if (strcasecmp(p1, "Serial") == 0) dcfg->auditlog_type = AUDITLOG_SERIAL; else if (strcasecmp(p1, "Concurrent") == 0) dcfg->auditlog_type = AUDITLOG_CONCURRENT; else return (const char *)apr_psprintf(cmd->pool, "Unrecognised parameter value for SecAuditLogType: %s", p1); return NULL; } static const char *cmd_audit_log_storage_dir(cmd_parms *cmd, void *in_dcfg, const char *p1) { sec_dir_config *dcfg = in_dcfg; dcfg->auditlog_storage_dir = ap_server_root_relative(cmd->pool, p1); return NULL; } static const char *cmd_audit_log_parts(cmd_parms *cmd, void *in_dcfg, const char *p1) { sec_dir_config *dcfg = in_dcfg; if (is_valid_parts_specification((char *)p1) != 1) { return apr_psprintf(cmd->pool, "Invalid parts specification for SecAuditLogParts: %s", p1); } dcfg->auditlog_parts = (char *)p1; return NULL; } static const char *cmd_scan_post(cmd_parms *cmd, void *in_dcfg, int flag) { sec_dir_config *dcfg = in_dcfg; dcfg->scan_post = flag; return NULL; } static const char *cmd_scan_output(cmd_parms *cmd, void *in_dcfg, int flag) { sec_dir_config *dcfg = in_dcfg; dcfg->scan_output = flag; return NULL; } static const char *cmd_default_action(cmd_parms *cmd, void *in_dcfg, const char *p1) { sec_dir_config *dcfg = in_dcfg; char *rc; dcfg->actionset = (actionset_t *)apr_pcalloc(cmd->pool, sizeof(actionset_t)); init_default_actionset(dcfg->actionset); rc = parse_actionset((char *)p1, dcfg->actionset, cmd->pool); if (rc != NULL) return rc; if ((dcfg->actionset->id != NULL)||(dcfg->actionset->rev != NULL) ||(dcfg->actionset->is_chained)||(dcfg->actionset->action == ACTION_SKIP) ) { return "Actions id, rev, chained, and skip are not allowed in SecFilterDefaultAction"; } return NULL; } static const char *cmd_signature_action(cmd_parms *cmd, void *in_dcfg, const char *p1) { sec_dir_config *dcfg = in_dcfg; char *rc; dcfg->actionset_signatures = (actionset_t *)apr_pcalloc(cmd->pool, sizeof(actionset_t)); init_default_actionset(dcfg->actionset_signatures); rc = parse_actionset((char *)p1, dcfg->actionset_signatures, cmd->pool); if (rc != NULL) return rc; if ((dcfg->actionset_signatures->id != NULL)||(dcfg->actionset_signatures->rev != NULL) ||(dcfg->actionset_signatures->is_chained)||(dcfg->actionset_signatures->action == ACTION_SKIP) ) { return "Actions id, rev, chained, and skip are not allowed in SecFilterSignatureAction"; } return NULL; } static const char *cmd_chroot_dir(cmd_parms *cmd, void *in_dcfg, const char *p1) { sec_srv_config *scfg = (sec_srv_config *)ap_get_module_config(cmd->server->module_config, &security_module); char cwd[1025] = ""; if (cmd->server->is_virtual) { return "SecChrootDir not allowed in VirtualHost"; } scfg->chroot_dir = (char *)p1; if (getcwd(cwd, 1024) == NULL) { return "SecChrootDir: failed to get the current working directory"; } if (chdir(scfg->chroot_dir) < 0) { return apr_psprintf(cmd->pool, "SecChrootDir: failed to chdir to \"%s\", errno=%d(%s)", scfg->chroot_dir, errno, strerror(errno)); } if (chdir(cwd) < 0) { return apr_psprintf(cmd->pool, "SecChrootDir: failed to chdir to \"%s\", errno=%d(%s)", cwd, errno, strerror(errno)); } return NULL; } static const char *cmd_chroot_lock(cmd_parms *cmd, void *in_dcfg, const char *p1) { sec_srv_config *scfg = (sec_srv_config *)ap_get_module_config(cmd->server->module_config, &security_module); if (cmd->server->is_virtual) { return "SecChrootLock not allowed in VirtualHost"; } scfg->chroot_lock = ap_server_root_relative(cmd->pool, p1); if (scfg->chroot_lock == NULL) { return "SecChrootLock: allocation failed"; } return NULL; } static const char *cmd_server_signature(cmd_parms *cmd, void *in_dcfg, const char *p1) { sec_srv_config *scfg = (sec_srv_config *)ap_get_module_config(cmd->server->module_config, &security_module); if (cmd->server->is_virtual) { return "SecServerSignature not allowed in VirtualHost"; } scfg->server_signature = (char *)p1; return NULL; } static const char *cmd_upload_dir(cmd_parms *cmd, void *in_dcfg, const char *p1) { sec_dir_config *dcfg = in_dcfg; if (strcasecmp(p1, "none") == 0) dcfg->upload_dir = NULL; else dcfg->upload_dir = ap_server_root_relative(cmd->pool, p1); return NULL; } static const char *cmd_upload_in_memory_limit(cmd_parms *cmd, void *in_dcfg, const char *p1) { sec_dir_config *dcfg = in_dcfg; dcfg->upload_in_memory_limit = atoi(p1); if (dcfg->upload_in_memory_limit < 0) return "Upload memory limit cannot be negative"; return NULL; } static const char *cmd_upload_keep_files(cmd_parms *cmd, void *in_dcfg, const char *p1) { sec_dir_config *dcfg = in_dcfg; if (strcasecmp(p1, "on") == 0) dcfg->upload_keep_files = KEEP_FILES_ON; else if (strcasecmp(p1, "off") == 0) dcfg->upload_keep_files = KEEP_FILES_OFF; else if (strcasecmp(p1, "relevantonly") == 0) dcfg->upload_keep_files = KEEP_FILES_RELEVANT_ONLY; else { return apr_psprintf(cmd->pool, "Unknown option: %s", p1); } return NULL; } static const char *cmd_upload_approve_script(cmd_parms *cmd, void *in_dcfg, const char *p1) { sec_dir_config *dcfg = in_dcfg; if (strcasecmp(p1, "none") == 0) dcfg->upload_approve_script = NULL; else dcfg->upload_approve_script = (char *)p1; /* TODO does the script exist? */ return NULL; } static const char *cmd_filter_output_mimetypes(cmd_parms *cmd, void *in_dcfg, const char *p1) { sec_dir_config *dcfg = in_dcfg; dcfg->scan_output_mimetypes = apr_psprintf(cmd->pool, " %s ", p1); strtolower(dcfg->scan_output_mimetypes); return NULL; } static char *create_per_rule_actionset(cmd_parms *cmd, sec_dir_config *dcfg, signature *sig, char *config, actionset_t *actionset) { char *rc = NULL; init_empty_actionset(actionset); if (config == NULL) { parse_actionset("", actionset, cmd->pool); return NULL; } rc = parse_actionset((char *)config, actionset, cmd->pool); if (rc != NULL) return rc; /* the id and msg actions can only be used on a rule that is * starting a chain, or on a standalone rule */ if ((actionset->mandatory)||(actionset->id != NULL) ||(actionset->rev != NULL)||(actionset->severity != NOT_SET) ) { signature **signatures = NULL, *previous_signature = NULL; int i; /* go back, ignoring placeholders, and look for the rule before this one */ signatures = (signature **)dcfg->signatures->elts; for (i = dcfg->signatures->nelts - 1; i >= 0; i--) { if (signatures[i]->is_inheritance_placeholder != 0) continue; previous_signature = signatures[i]; break; } if ((previous_signature != NULL) && (previous_signature->actionset != NULL) && (previous_signature->actionset->is_chained)) { if (actionset->mandatory) return "Action \"mandatory\" cannot be used on a chained rule that did not start the chain"; if (actionset->id != NULL) return "Action \"id\" cannot be used on a chained rule that did not start the chain"; if (actionset->rev != NULL) return "Action \"rev\" cannot be used on a chained rule that did not start the chain"; if (actionset->severity != NOT_SET) return "Action \"severity\" cannot be used on a chained rule that did not start the chain"; } } return NULL; } static const char *cmd_filter(cmd_parms *cmd, void *in_dcfg, const char *p1, const char *p2) { sec_dir_config *dcfg = in_dcfg; signature *sig; sig = apr_pcalloc(cmd->pool, sizeof(signature)); if (sig == NULL) return FATAL_ERROR; sig->actions_restricted = dcfg->actions_restricted; sig->actionset = NULL; /* p1 is the regular expression string */ if (p1[0] == '!') { sig->is_allow = 1; sig->pattern = (char *)p1; sig->regex = ap_pregcomp(cmd->pool, p1 + 1, REG_ICASE | REG_NOSUB); } else { sig->pattern = (char *)p1; sig->regex = ap_pregcomp(cmd->pool, p1, REG_ICASE | REG_NOSUB); } if (sig->regex == NULL) { return apr_psprintf(cmd->pool, "Invalid regular expression: %s", sig->pattern); } if (p2 != NULL) { actionset_t *signature_actionset = NULL; char *error_message = NULL; signature_actionset = (actionset_t *)apr_pcalloc(cmd->pool, sizeof(actionset_t)); if (dcfg->actionset_signatures != NOT_SET_P) { error_message = create_per_rule_actionset(cmd, dcfg, sig, (char *)p2, signature_actionset); if (error_message != NULL) return error_message; sig->actionset = merge_actionsets(cmd->pool, dcfg->actionset_signatures, signature_actionset, dcfg->actions_restricted); if (sig->actionset == NULL) return "Failed to merge actionsets"; } else { actionset_t temporary_actionset; init_default_actionset(&temporary_actionset); error_message = create_per_rule_actionset(cmd, dcfg, sig, (char *)p2, signature_actionset); if (error_message != NULL) return error_message; sig->actionset = merge_actionsets(cmd->pool, &temporary_actionset, signature_actionset, dcfg->actions_restricted); if (sig->actionset == NULL) return "Failed to merge actionsets"; } if ((sig->actionset->action == ACTION_SKIP)&&(sig->actionset->is_chained)) { return "Not possible to use \"skip\" with a chained rule"; } } else { /* The rules that do not specify custom actions need to * inherit from the SecFilterSignatureAction too. */ if (dcfg->actionset_signatures != NOT_SET_P) { sig->actionset = (actionset_t *)apr_pcalloc(cmd->pool, sizeof(actionset_t)); memcpy(sig->actionset, dcfg->actionset_signatures, sizeof(actionset_t)); } } /* find a pointer to the first rule in the chain */ if (dcfg->signatures->nelts != 0) { signature **psignatures = (signature **)dcfg->signatures->elts; signature *prevsig = psignatures[dcfg->signatures->nelts - 1]; if ((prevsig->actionset != NULL)&&(prevsig->actionset->is_chained)) { if (prevsig->first_sig_in_chain != NULL) sig->first_sig_in_chain = prevsig->first_sig_in_chain; else sig->first_sig_in_chain = prevsig; } } /* add the signature to the list of all signatures */ *(signature **)apr_array_push(dcfg->signatures) = sig; return NULL; } static const char *cmd_filter_debug_log(cmd_parms *cmd, void *in_dcfg, const char *p1) { sec_dir_config *dcfg = in_dcfg; int rc; dcfg->debuglog_name = ap_server_root_relative(cmd->pool, p1); rc = apr_file_open(&dcfg->debuglog_fd, dcfg->debuglog_name, APR_WRITE | APR_APPEND | APR_CREATE | APR_BINARY, CREATEMODE, cmd->pool); if (rc != APR_SUCCESS) { return apr_psprintf(cmd->pool, "mod_security: Failed to open the debug log file: %s", dcfg->debuglog_name); } return NULL; } static const char *cmd_filter_debug_level(cmd_parms *cmd, void *in_dcfg, const char *p1) { sec_dir_config *dcfg = in_dcfg; dcfg->filter_debug_level = atoi(p1); return NULL; } static const char *cmd_filter_selective(cmd_parms *cmd, void *in_dcfg, const char *p1, const char *p2, const char *p3) { sec_dir_config *dcfg = in_dcfg; char *p, *t, *saveptr; signature *sig; /* initialise the structure first */ sig = apr_pcalloc(cmd->pool, sizeof(signature)); if (sig == NULL) return FATAL_ERROR; sig->is_allow = 0; sig->is_selective = 1; sig->is_negative = 0; sig->requires_parsed_args = 0; sig->actions_restricted = dcfg->actions_restricted; sig->actionset = NULL; sig->variables = apr_array_make(cmd->pool, 10, sizeof (variable *)); if (p2[0] == '!') { sig->is_allow = 1; sig->pattern = (char *)p2; sig->regex = ap_pregcomp(cmd->pool, p2 + 1, REG_ICASE | REG_NOSUB); } else { sig->pattern = (char *)p2; sig->regex = ap_pregcomp(cmd->pool, p2, REG_ICASE | REG_NOSUB); } if (sig->regex == NULL) { return apr_psprintf(cmd->pool, "Invalid regular expression: %s", sig->pattern); } /* split parameter 1 apart and extract variable names */ p = strdup(p1); t = strtok_r(p, "|", &saveptr); while (t != NULL) { char *x = t; /* add the token to the list */ variable *v = (variable *)apr_pcalloc(cmd->pool, sizeof(variable)); if (v == NULL) return FATAL_ERROR; v->type = VAR_UNKNOWN; v->name = NULL; /* when ! is the first character in the variable * name, that means that the restrictions need to be * relaxed for that variable (within the filter scope) */ if (t[0] == '!') { v->action = VAR_ACTION_ALLOW; sig->is_negative = 1; sig->requires_parsed_args = 1; x++; } else { v->action = VAR_ACTION_DENY; } /* arguments */ if (strncmp(x, "ARG_", 4) == 0) { v->type = VAR_ARG; v->name = apr_pstrdup(cmd->pool, x + 4); sig->requires_parsed_args = 1; } /* HTTP headers */ else if (strncmp(x, "HTTP_", 5) == 0) { char *px; v->type = VAR_HEADER; v->name = apr_pstrdup(cmd->pool, x + 5); /* replace all "_" with "-" */ px = v->name; while (*px != 0) { if (*px == '_') *px = '-'; px++; } } /* HTTP headers again, but a different name */ else if (strncmp(x, "HEADER_", 7) == 0) { char *px; v->type = VAR_HEADER; v->name = apr_pstrdup(cmd->pool, x + 7); /* replace all "_" with "-" */ px = v->name; while (*px != 0) { if (*px == '_') *px = '-'; px++; } } /* custom file name */ else if (strncmp(x, "FILE_NAME_", 10) == 0) { v->type = VAR_FILE_NAME; v->name = apr_pstrdup(cmd->pool, x + 10); } /* custom file size */ else if (strncmp(x, "FILE_SIZE_", 10) == 0) { v->type = VAR_FILE_SIZE; v->name = apr_pstrdup(cmd->pool, x + 10); } /* COOKIES */ else if (strncmp(x, "COOKIE_", 7) == 0) { v->type = VAR_COOKIE; v->name = apr_pstrdup(cmd->pool, x + 7); } /* environment variables */ else if (strncmp(x, "ENV_", 4) == 0) { v->type = VAR_ENV; v->name = apr_pstrdup(cmd->pool, x + 4); } /* all arguments */ else if (strcmp (x, "ARGS") == 0) { v->type = VAR_ARGS; v->name = apr_pstrdup(cmd->pool, x); } /* just the post payload */ else if (strcmp(x, "POST_PAYLOAD") == 0) { v->type = VAR_POST_PAYLOAD; v->name = apr_pstrdup(cmd->pool, x); } else if (strcmp(x, "OUTPUT") == 0) { v->type = VAR_OUTPUT; v->name = apr_pstrdup(cmd->pool, x); sig->is_output = PHASE_OUTPUT; } else if (strcmp(x, "OUTPUT_STATUS") == 0) { v->type = VAR_OUTPUT_STATUS; sig->is_output = PHASE_OUTPUT; } /* everything else */ else { int i = 0; while (all_variables[i] != NULL) { if (strcmp(all_variables[i], x) == 0) { v->type = i; /* v->name = apr_pstrdup(cmd->pool, x); */ break; } i++; } } if (v->type == VAR_UNKNOWN) { v->name = apr_pstrdup(cmd->pool, "UKNOWN"); return apr_psprintf(cmd->pool, "Unknown variable name: %s", x); } if ((v->type == VAR_ARGS_NAMES)||(v->type == VAR_ARGS_VALUES)) sig->requires_parsed_args = 1; *(variable **)apr_array_push(sig->variables) = v; /* and proceed to the next token */ t = strtok_r(NULL, "|", &saveptr); } free(p); if (p3 != NULL) { actionset_t *signature_actionset = NULL; char *error_message = NULL; signature_actionset = (actionset_t *)apr_pcalloc(cmd->pool, sizeof(actionset_t)); if (dcfg->actionset_signatures != NOT_SET_P) { error_message = create_per_rule_actionset(cmd, dcfg, sig, (char *)p3, signature_actionset); if (error_message != NULL) return error_message; sig->actionset = merge_actionsets(cmd->pool, dcfg->actionset_signatures, signature_actionset, dcfg->actions_restricted); if (sig->actionset == NULL) return "Failed to merge actionsets"; } else { actionset_t temporary_actionset; init_default_actionset(&temporary_actionset); error_message = create_per_rule_actionset(cmd, dcfg, sig, (char *)p3, signature_actionset); if (error_message != NULL) return error_message; sig->actionset = merge_actionsets(cmd->pool, &temporary_actionset, signature_actionset, dcfg->actions_restricted); if (sig->actionset == NULL) return "Failed to merge actionsets"; } if ((sig->actionset->action == ACTION_SKIP)&&(sig->actionset->is_chained)) { return "Not possible to use \"skip\" with a chained rule"; } } else { /* The rules that do not specify custom actions need to * inherit from the SecFilterSignatureAction too. */ if (dcfg->actionset_signatures != NOT_SET_P) { sig->actionset = (actionset_t *)apr_pcalloc(cmd->pool, sizeof(actionset_t)); memcpy(sig->actionset, dcfg->actionset_signatures, sizeof(actionset_t)); } } /* find a pointer to the first rule in the chain */ if (dcfg->signatures->nelts != 0) { signature **psignatures = (signature **)dcfg->signatures->elts; signature *prevsig = psignatures[dcfg->signatures->nelts - 1]; if ((prevsig->actionset != NULL)&&(prevsig->actionset->is_chained)) { if (prevsig->first_sig_in_chain != NULL) sig->first_sig_in_chain = prevsig->first_sig_in_chain; else sig->first_sig_in_chain = prevsig; } } /* add the signature to the list of all signatures */ *(signature **)apr_array_push(dcfg->signatures) = sig; return NULL; } static const char *cmd_normalize_cookies(cmd_parms *cmd, void *in_dcfg, int flag) { sec_dir_config *dcfg = in_dcfg; dcfg->normalize_cookies = flag; return NULL; } static const char *cmd_check_cookie_format(cmd_parms *cmd, void *in_dcfg, int flag) { sec_dir_config *dcfg = in_dcfg; dcfg->check_cookie_format = flag; return NULL; } static const char *cmd_cookie_format(cmd_parms *cmd, void *in_dcfg, const char *p1) { sec_dir_config *dcfg = in_dcfg; if (strcmp(p1, "0") == 0) dcfg->cookie_format = COOKIES_V0; else if (strcmp(p1, "1") == 0) dcfg->cookie_format = COOKIES_V1; else { return apr_psprintf(cmd->pool, "Unknown cookie format: %s", p1); } return NULL; } static const char *cmd_charset(cmd_parms *cmd, void *in_dcfg, const char *p1) { sec_dir_config *dcfg = in_dcfg; dcfg->charset_id = convert_charset_to_id((char *)p1); if (dcfg->charset_id == -1) { return apr_psprintf(cmd->pool, "Unknown charset: %s", p1); } return NULL; } static const char *cmd_filter_import(cmd_parms *cmd, void *in_dcfg, const char *p1) { sec_dir_config *dcfg = in_dcfg; signature *sig; /* initialise the structure first */ sig = apr_pcalloc(cmd->pool, sizeof(signature)); if (sig == NULL) return FATAL_ERROR; sig->is_inheritance_placeholder = INHERITANCE_IMPORT; sig->inheritance_id = p1; /* add the signature to the list of all signatures */ *(signature **)apr_array_push(dcfg->signatures) = sig; return NULL; } static const char *cmd_filter_remove(cmd_parms *cmd, void *in_dcfg, const char *p1) { sec_dir_config *dcfg = in_dcfg; signature *sig; /* initialise the structure first */ sig = apr_pcalloc(cmd->pool, sizeof(signature)); if (sig == NULL) return FATAL_ERROR; sig->is_inheritance_placeholder = INHERITANCE_REMOVE; sig->inheritance_id = p1; /* add the signature to the list of all signatures */ *(signature **)apr_array_push(dcfg->signatures) = sig; return NULL; } static const char *cmd_filter_inheritance_mandatory(cmd_parms *cmd, void *in_dcfg, int flag) { sec_dir_config *dcfg = in_dcfg; dcfg->inheritance_mandatory = flag; return NULL; } static const char *cmd_filter_actions_restricted(cmd_parms *cmd, void *in_dcfg, int flag) { sec_dir_config *dcfg = in_dcfg; dcfg->actions_restricted = flag; return NULL; } static const char *cmd_guardian_log(cmd_parms *cmd, void *in_dcfg, const char *p1, const char *p2) { sec_srv_config *scfg = (sec_srv_config *)ap_get_module_config(cmd->server->module_config, &security_module); if (cmd->server->is_virtual) { return "SecGuardianLog not allowed in VirtualHost"; } if (p2 != NULL) { if (strncmp(p2, "env=", 4) != 0) return "Error in condition clause"; if ( (p2[4] == '\0') || ((p2[4] == '!')&&(p2[5] == '\0')) ) return "Missing variable name"; scfg->guardian_log_condition = apr_pstrdup(cmd->pool, p2 + 4); } scfg->guardian_log_name = (char *)p1; if (scfg->guardian_log_name[0] == '|') { const char *pipe_name = ap_server_root_relative(cmd->pool, scfg->guardian_log_name + 1); piped_log *pipe_log; pipe_log = ap_open_piped_log(cmd->pool, pipe_name); if (pipe_log == NULL) { return apr_psprintf(cmd->pool, "mod_security: Failed to open the guardian log pipe: %s", pipe_name); } scfg->guardian_log_fd = ap_piped_log_write_fd(pipe_log); } else { const char *file_name = ap_server_root_relative(cmd->pool, scfg->guardian_log_name); apr_status_t rc; rc = apr_file_open(&scfg->guardian_log_fd, file_name, APR_WRITE | APR_APPEND | APR_CREATE | APR_BINARY, CREATEMODE, cmd->pool); if (rc != APR_SUCCESS) { return apr_psprintf(cmd->pool, "mod_security: Failed to open the guardian log file: %s", file_name); } } return NULL; } static const char *cmd_audit_log_relevant_status(cmd_parms *cmd, void *in_dcfg, const char *p1) { sec_dir_config *dcfg = in_dcfg; dcfg->auditlog_relevant_regex = ap_pregcomp(cmd->pool, p1, REG_ICASE | REG_NOSUB); if (dcfg->auditlog_relevant_regex == NULL) { return apr_psprintf(cmd->pool, "Invalid regular expression: %s", p1); } return NULL; } static const command_rec sec_cmds[] = { AP_INIT_TAKE12 ( "SecFilter", cmd_filter, NULL, CMD_SCOPE_ANY, "The filtering expression" ), AP_INIT_TAKE1 ( "SecFilterDebugLog", cmd_filter_debug_log, NULL, CMD_SCOPE_ANY, "The filename of the filter debugging log file" ), AP_INIT_TAKE1 ( "SecFilterDebugLevel", cmd_filter_debug_level, NULL, CMD_SCOPE_ANY, "The level of the debugging log file verbosity" ), AP_INIT_TAKE23 ( "SecFilterSelective", cmd_filter_selective, NULL, CMD_SCOPE_ANY, "The variable representing areas where filtering is wanted, the filtering regular expression and optional action to take on match" ), AP_INIT_TAKE1 ( "SecFilterEngine", cmd_filter_engine, NULL, CMD_SCOPE_ANY, "On, Off, or DynamicOnly to determine when will request be filtered" ), AP_INIT_FLAG ( "SecServerResponseToken", cmd_server_response_token, NULL, RSRC_CONF, "On or Off to set whether the mod_security token will appear in the server signature" ), AP_INIT_FLAG ( "SecFilterScanPOST", cmd_scan_post, NULL, CMD_SCOPE_ANY, "On or Off to set whether a request body will be processed" ), AP_INIT_FLAG ( "SecFilterScanOutput", cmd_scan_output, NULL, CMD_SCOPE_ANY, "On or Off to set whether output will be scanned too" ), AP_INIT_TAKE1 ( "SecFilterDefaultAction", cmd_default_action, NULL, CMD_SCOPE_ANY, "The default action to take on rule match" ), AP_INIT_TAKE1 ( "SecFilterSignatureAction", cmd_signature_action, NULL, CMD_SCOPE_ANY, "Base action template for signatures that follow this directive" ), AP_INIT_TAKE1 ( "SecAuditEngine", cmd_audit_engine, NULL, CMD_SCOPE_ANY, "On, Off, RelevantOnly or DynamicOrRelevent to determine the level of audit logging" ), AP_INIT_TAKE1 ( "SecAuditLog", cmd_audit_log, NULL, CMD_SCOPE_ANY, "The filename of the audit log file" ), AP_INIT_TAKE1 ( "SecChrootDir", cmd_chroot_dir, NULL, RSRC_CONF, "The path of the directory to which server will be chrooted" ), AP_INIT_TAKE1 ( "SecChrootLock", cmd_chroot_lock, NULL, RSRC_CONF, "The filename of the lock file used during the chroot process. Defaults to \"logs/modsec_chroot.lock\"" ), AP_INIT_TAKE1 ( "SecServerSignature", cmd_server_signature, NULL, RSRC_CONF, "The new signature of the server" ), AP_INIT_TAKE1 ( "SecFilterOutputMimeTypes", cmd_filter_output_mimetypes, NULL, CMD_SCOPE_ANY, "The list of mime types that will be scanned on output" ), AP_INIT_FLAG ( "SecFilterInheritance", cmd_filter_inheritance, NULL, CMD_SCOPE_ANY, "On or Off to set whether rules from the parent context will be inherited" ), AP_INIT_FLAG ( "SecFilterCheckURLEncoding", cmd_filter_check_encoding, NULL, CMD_SCOPE_ANY, "On or Off to set whether URL encoding validation will be performed" ), AP_INIT_FLAG ( "SecFilterCheckUnicodeEncoding", cmd_filter_check_unicode_encoding, NULL, CMD_SCOPE_ANY, "On or Off to set whether Unicode encoding validation will be performed" ), AP_INIT_TAKE2 ( "SecFilterForceByteRange", cmd_filter_force_byte_range, NULL, CMD_SCOPE_ANY, "The first and the last byte value of the range that will be accepted" ), AP_INIT_TAKE1 ( "SecUploadDir", cmd_upload_dir, NULL, CMD_SCOPE_ANY, "The path to the directory where uploaded files should be stored" ), AP_INIT_TAKE1 ( "SecUploadInMemoryLimit", cmd_upload_in_memory_limit, NULL, RSRC_CONF, "The maximal size of memory used for storing multipart/form-data requests" ), AP_INIT_TAKE1 ( "SecUploadKeepFiles", cmd_upload_keep_files, NULL, CMD_SCOPE_ANY, "On or Off to choose whether to keep the uploaded files or not" ), AP_INIT_TAKE1 ( "SecUploadApproveScript", cmd_upload_approve_script, NULL, CMD_SCOPE_ANY, "The path to the script that will be called to approve every uploaded file" ), AP_INIT_FLAG ( "SecFilterNormalizeCookies", cmd_normalize_cookies, NULL, CMD_SCOPE_ANY, "On or Off to determine whether cookie values will be normalized for testing, defaults to On" ), AP_INIT_FLAG ( "SecFilterCheckCookieFormat", cmd_check_cookie_format, NULL, CMD_SCOPE_ANY, "On or Off to determine whether cookie format will be checked, defaults to On" ), AP_INIT_TAKE1 ( "SecFilterCookieFormat", cmd_cookie_format, NULL, CMD_SCOPE_ANY, "version of the Cookie specification to use for parsing. Possible values are 0 and 1." ), AP_INIT_TAKE1 ( "SecCharset", cmd_charset, NULL, CMD_SCOPE_ANY, "Configures the charset" ), AP_INIT_ITERATE ( "SecFilterImport", cmd_filter_import, NULL, CMD_SCOPE_ANY, "imports a rule from the parent configuration context." ), AP_INIT_ITERATE ( "SecFilterRemove", cmd_filter_remove, NULL, CMD_SCOPE_ANY, "removes a rule that was inherited from the parent configuration context." ), AP_INIT_FLAG ( "SecFilterInheritanceMandatory", cmd_filter_inheritance_mandatory, NULL, CMD_SCOPE_ANY, "when this directive is set to On then the rules in the parent context cannot be removed from a child context." ), AP_INIT_TAKE12 ( "SecGuardianLog", cmd_guardian_log, NULL, RSRC_CONF, "The filename of the filter debugging log file" ), AP_INIT_TAKE1 ( "SecAuditLogType", cmd_audit_log_type, NULL, CMD_SCOPE_ANY, "whether to use the old audit log format (Serial) or new (Concurrent)" ), AP_INIT_TAKE1 ( "SecAuditLogStorageDir", cmd_audit_log_storage_dir, NULL, CMD_SCOPE_ANY, "path to the audit log storage area; absolute, or relative to the root of the server" ), AP_INIT_TAKE1 ( "SecAuditLogParts", cmd_audit_log_parts, NULL, CMD_SCOPE_ANY, "list of audit log parts that go into the log." ), AP_INIT_TAKE1 ( "SecAuditLogRelevantStatus", cmd_audit_log_relevant_status, NULL, CMD_SCOPE_ANY, "regular expression that will be used to determine if the response status is relevant for audit logging" ), AP_INIT_FLAG ( "SecFilterActionsRestricted", cmd_filter_actions_restricted, NULL, CMD_SCOPE_ANY, "whether to allow rules to override SecFiltersDefaultAction configuration" ), { NULL } }; /* -- Log functions (PLOGGING) --------------------------------------------- */ int is_response_status_relevant(request_rec *r, sec_dir_config *dcfg, int status) { char *status_as_string; int regex_result; if (dcfg == NULL) return 0; if ((dcfg->auditlog_relevant_regex == NULL)||(dcfg->auditlog_relevant_regex == NOT_SET_P)) return 0; status_as_string = apr_psprintf(r->pool, "%i", status); if (status_as_string == NULL) return 0; regex_result = ap_regexec(dcfg->auditlog_relevant_regex, status_as_string, 0, NULL, 0); if (regex_result == REG_NOMATCH) { sec_debug_log(r, 4, "Audit log: Status %i considered not relevant", status); return 0; } sec_debug_log(r, 3, "Audit log: Status %i considered relevant", status); return 1; } /** * This function is the main entry point for logging. */ static int sec_logger(request_rec *r) { const apr_array_header_t *arr = NULL; request_rec *origr = NULL; modsec_rec *msr = NULL; sec_debug_log(r, 2, "Logging phase starting"); /* -- Initialise logging -- */ /* Find the first (origr) and the last (r) request */ origr = r; while(origr->prev) { origr = origr->prev; } while(r->next) { r = r->next; } /* At this point r is the last request in the * chain. However, we now need to detect a case when * a bad ErrorDocument was used and back out of it. That's * how Apache does it internally. Except where Apache knows * exactly what is happening we will have to rely on the missing * headers in the final request to detect this condition. */ arr = apr_table_elts(r->headers_out); while ((arr->nelts == 0)&&(r->prev != NULL)) { r = r->prev; arr = apr_table_elts(r->headers_out); } /* Find the main context */ msr = find_msr(r); /* msr may be null in cases where Apache encountered an * invalid request. Such requests will not go through all * processing stages, but will go through the logging phase. */ if (msr == NULL) { msr = sec_create_context(origr); } if (msr->dcfg == NULL) return DECLINED; /* -- Guardian -- */ sec_guardian_logger(r, origr, msr); /* -- Audit logging starts here -- */ /* Do not log anything if we were specifically asked not to */ if (msr->explicit_auditlog == 0) { sec_debug_log(r, 4, "Audit log: Not logging because asked not to"); return DECLINED; } /* We are processing the switch statement only if we * are not aware about any explicit instructions about * audit logging. The explicit instructions always * override the configuration settings. */ if (msr->explicit_auditlog == NOT_SET) { if ((r->handler != NULL)||(origr->handler != NULL)) msr->is_dynamic = 1; else msr->is_dynamic = 0; if ( (is_response_status_relevant(r, msr->dcfg, origr->status)) || (is_response_status_relevant(r, msr->dcfg, r->status)) ) { msr->is_relevant++; } switch(msr->dcfg->auditlog_flag) { case AUDITLOG_OFF : sec_debug_log(r, 3, "Audit log: Set to Off - skipping"); return DECLINED; break; case AUDITLOG_DYNAMIC_OR_RELEVANT : if ((msr->is_dynamic == 0)&&(msr->is_relevant == 0)) { sec_debug_log(r, 3, "Audit log: Set to DynamicOrRelevant - ignoring a non-dynamic and non-relevant request"); return DECLINED; } break; case AUDITLOG_RELEVANT_ONLY : if (msr->is_relevant == 0) { sec_debug_log(r, 3, "Audit log: Set to RelevantOnly - ignoring a non-relevant request"); return DECLINED; } break; case AUDITLOG_ON : /* All right, do nothing */ break; default : sec_debug_log(r, 1, "Audit log: Internal Error - unknown setting detected (%i)", msr->dcfg->auditlog_flag); return DECLINED; break; } } sec_auditlog_init(msr); /* return immediatelly if we don't have a file to write to */ if ((msr->dcfg->auditlog_fd == NULL)||(msr->dcfg->auditlog_fd == NOT_SET_P)) { sec_debug_log(r, 1, "Audit log enabled, but filename not specified, uri=\"%s\"", log_escape(r->pool, r->uri)); return DECLINED; } if (msr->dcfg->auditlog_type == AUDITLOG_CONCURRENT) { sec_audit_logger_concurrent(r, origr, msr->dcfg, msr); } else { sec_audit_logger_serial(r, origr, msr->dcfg, msr); } /* -- Done logging -- */ /* It doesn't matter what we return, Apache will * execute all handlers registered with the logging * hook. */ return DECLINED; } /** * Construct a log line in the vcombinedus format (see below). */ static char *construct_log_vcombinedus(request_rec *r, request_rec *origr) { char *local_user, *remote_user; char *referer, *user_agent, *uniqueid; char *sessionid = "-"; /* not used yet */ /* remote log name */ if (r->connection->remote_logname == NULL) remote_user = "-"; else remote_user = r->connection->remote_logname; /* authenticated user */ if (r->user == NULL) local_user = "-"; else local_user = r->user; /* unique id */ uniqueid = (char *)get_env_var(r, "UNIQUE_ID"); if (uniqueid == NULL) uniqueid = "-"; /* referer */ referer = (char *)apr_table_get(r->headers_in, "Referer"); if (referer == NULL) referer = "-"; /* user agent */ user_agent = (char *)apr_table_get(r->headers_in, "User-Agent"); if (user_agent == NULL) user_agent = "-"; return apr_psprintf(r->pool, "%s %s %s %s [%s] \"%s\" %i %" APR_OFF_T_FMT " \"%s\" \"%s\" %s \"%s\"", ap_get_server_name(r), r->connection->remote_ip, log_escape(r->pool, remote_user), log_escape(r->pool, local_user), current_logtime(r), ((origr->the_request == NULL) ? "" : log_escape(r->pool, origr->the_request)), origr->status, r->bytes_sent, log_escape(r->pool, referer), log_escape(r->pool, user_agent), log_escape(r->pool, uniqueid), sessionid); } static char *construct_log_vcombinedus_limited(request_rec *r, request_rec *origr, int _limit, int *was_limited) { char *local_user, *remote_user; char *referer, *user_agent, *uniqueid; char *sessionid = "-"; /* not used yet */ char *the_request, *bytes_sent; int limit = _limit; /* remote log name */ if (r->connection->remote_logname == NULL) remote_user = "-"; else remote_user = log_escape_nq(r->pool, r->connection->remote_logname); /* authenticated user */ if (r->user == NULL) local_user = "-"; else local_user = log_escape_nq(r->pool, r->user); /* unique id */ uniqueid = (char *)get_env_var(r, "UNIQUE_ID"); if (uniqueid == NULL) uniqueid = "-"; else uniqueid = log_escape(r->pool, uniqueid); /* referer */ referer = (char *)apr_table_get(r->headers_in, "Referer"); if (referer == NULL) referer = "-"; else referer = log_escape(r->pool, referer); /* user agent */ user_agent = (char *)apr_table_get(r->headers_in, "User-Agent"); if (user_agent == NULL) user_agent = "-"; else user_agent = log_escape(r->pool, user_agent); the_request = (origr->the_request == NULL) ? "" : log_escape(r->pool, origr->the_request); bytes_sent = apr_psprintf(r->pool, "%" APR_OFF_T_FMT, r->bytes_sent); /* first take away the size of the * information we must log */ limit -= 22; /* spaces and double quotes */ limit -= strlen(ap_get_server_name(r)); /* server name or IP */ limit -= strlen(r->connection->remote_ip); /* remote IP */ limit -= 28; /* current_logtime */ limit -= 3; /* status */ limit -= strlen(bytes_sent); /* bytes sent */ limit -= strlen(uniqueid); /* unique id */ limit -= strlen(sessionid); /* session id */ if (limit <= 0) { sec_debug_log(r, 1, "GuardianLog: Atomic pipe write size too small: %i", PIPE_BUF); return NULL; } /* we hope to be able to squeeze everything in */ if (limit < (int)(strlen(remote_user) + strlen(local_user) + strlen(referer) + strlen(user_agent) + strlen(the_request))) { /* Boo hoo, there is not enough space available. */ *was_limited = 1; /* see if we can reduce the size of something */ if (strlen(remote_user) > 32) { sec_debug_log(r, 9, "GuardianLog: Reduced remote_user to 32"); remote_user[32] = '\0'; } limit -= strlen(remote_user); if (strlen(local_user) > 32) { sec_debug_log(r, 9, "GuardianLog: Reduced local_user to 32"); local_user[32] = '\0'; } limit -= strlen(local_user); if (strlen(referer) > 64) { sec_debug_log(r, 9, "GuardianLog: Reduced referer to 64"); referer[64] = '\0'; } limit -= strlen(referer); if (strlen(user_agent) > 64) { sec_debug_log(r, 9, "GuardianLog: Reduced user_agent to 64"); user_agent[64] = '\0'; } limit -= strlen(user_agent); if (limit <= 0) { sec_debug_log(r, 1, "GuardianLog: Atomic pipe write size too small: %i", PIPE_BUF); return NULL; } /* use what's left for the request line */ if ((int)strlen(the_request) > limit) { the_request[limit] = '\0'; sec_debug_log(r, 9, "GuardianLog: Reduced the_request to %i bytes", limit); } } else { /* Yay! We have enough space! */ *was_limited = 0; } return apr_psprintf(r->pool, "%s %s %s %s [%s] \"%s\" %i %s \"%s\" \"%s\" %s \"%s\"", ap_get_server_name(r), r->connection->remote_ip, remote_user, local_user, current_logtime(r), the_request, origr->status, bytes_sent, referer, user_agent, uniqueid, sessionid ); } /** * The guardian logger is used to interface to the external * script for web server protection - httpd_guardian. */ static void sec_guardian_logger(request_rec *r, request_rec *origr, modsec_rec *msr) { sec_srv_config *scfg = (sec_srv_config *)ap_get_module_config(r->server->module_config, &security_module); char *str1, *str2, *text; char *modsec_message = "-"; int modsec_rating = 0; /* not used yet */ apr_size_t nbytes, nbytes_written; apr_time_t duration = (apr_time_now() - origr->request_time); int limit, was_limited; /* bail out if we do not have where to write */ if ((scfg->guardian_log_name == NULL)||(scfg->guardian_log_fd == NULL)) return; /* process the condition, if we have one */ if (scfg->guardian_log_condition != NULL) { if (*scfg->guardian_log_condition == '!') { if (apr_table_get(r->subprocess_env, scfg->guardian_log_condition + 1) != NULL) { /* TODO log message */ return; } } else { if (apr_table_get(r->subprocess_env, scfg->guardian_log_condition) == NULL) { /* TODO log message */ return; } } } /* * Log format is as follows: * * %V %h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-agent}i" %{UNIQUE_ID}e * "SESSION_ID" %T %D "MODSEC_MESSAGE" MODSEC_RATING * * The fields SESSION_ID, MODSEC_MESSAGE, and MODSEC_RATING are not used at the moment. */ str2 = apr_psprintf(r->pool, "%" APR_TIME_T_FMT " %" APR_TIME_T_FMT " \"%s\" %i", duration, apr_time_sec(duration), log_escape(r->pool, modsec_message), modsec_rating); if (str2 == NULL) return; /* If we are logging to a pipe we need to observe and * obey the pipe atomic write limit - PIPE_BUF. */ was_limited = 0; if (scfg->guardian_log_name[0] == '|') { /* According to "Advanced Programming in the UNIX Environment * PIPE_BUF is 4096 on Linux 2.4.22, 8192 on Mac OSX 10.3, 9216 * on Solaris 9, and 16384 on FreeBSD 5.2.1. It also cannot be * less than 512 on any POSIX system. In our case, even 512 bytes * should be more than enough to avoid seeing the "atomic pipe * write size too small" message. */ limit = PIPE_BUF - strlen(str2) - 5; if (limit <= 0) { sec_debug_log(r, 1, "GuardianLog: Atomic pipe write size too small: %i", PIPE_BUF); return; } str1 = construct_log_vcombinedus_limited(r, origr, limit, &was_limited); if (str1 == NULL) return; } else { str1 = construct_log_vcombinedus(r, origr); if (str1 == NULL) return; } if (was_limited == 0) text = apr_psprintf(r->pool, "%s %s\n", str1, str2); else text = apr_psprintf(r->pool, "%s %s L\n", str1, str2); if (text == NULL) return; nbytes = strlen(text); apr_file_write_full(scfg->guardian_log_fd, text, nbytes, &nbytes_written); } const char *get_response_protocol(request_rec *r) { int proto_num = r->proto_num; if (r->assbackwards) { return NULL; } if (proto_num > HTTP_VERSION(1,0) && apr_table_get(r->subprocess_env, "downgrade-1.0")) { proto_num = HTTP_VERSION(1,0); } if (proto_num == HTTP_VERSION(1,0) && apr_table_get(r->subprocess_env, "force-response-1.0")) { return "HTTP/1.0"; } return AP_SERVER_PROTOCOL; } /** * New-style (concurrent) audit logger. */ void sec_audit_logger_concurrent(request_rec *r, request_rec *origr, sec_dir_config *dcfg, modsec_rec *msr) { const apr_array_header_t *arr = NULL; apr_table_entry_t *te = NULL; char *str1 = NULL, *str2 = NULL, *text = NULL; apr_size_t nbytes, nbytes_written; unsigned char md5hash[APR_MD5_DIGESTSIZE]; int i, reconstructed_request_body_flag = 0; int was_limited = 0; int wrote_response_body = 0; sec_debug_log(r, 4, "sec_audit_logger_concurrent: Starting"); /* No fd means no logging for this request */ if (msr->new_auditlog_fd == NULL) return; /* AUDITLOG_PART_REQUEST_BODY */ if (strchr(msr->dcfg->auditlog_parts, AUDITLOG_PART_REQUEST_BODY) != NULL) { char *text = NULL; if ((msr->is_body_read != 0)&&(msr->ctx_in != NULL)) { text = apr_psprintf(r->pool, "\n--%s-C--\n", msr->new_auditlog_boundary); sec_auditlog_write(msr, text, strlen(text)); if (msr->ctx_in->type == POST_IN_MEMORY) { sec_auditlog_write(msr, msr->ctx_in->buffer, msr->ctx_in->buflen); } else { char *buffer = NULL; /* request body is on disk and we can't write it * to the audit log; reconstruct urlencoded body instead */ buffer = multipart_reconstruct_urlencoded_body(msr->mpd); if (buffer != NULL) { sec_auditlog_write(msr, buffer, strlen(buffer)); reconstructed_request_body_flag = 1; } else { sec_debug_log(r, 1, "Audit log: Failed to reconstruct request body"); } } } } /* AUDITLOG_PART_A_RESPONSE_HEADERS */ if (strchr(msr->dcfg->auditlog_parts, AUDITLOG_PART_A_RESPONSE_HEADERS) != NULL) { text = apr_psprintf(r->pool, "\n--%s-F--\n", msr->new_auditlog_boundary); sec_auditlog_write(msr, text, strlen(text)); /* There are no response headers (or the status line) in HTTP 0.9 */ if (!r->assbackwards) { const char *status_line = (r->status_line != NULL) ? r->status_line : ap_get_status_line(r->status); const char *protocol = get_response_protocol(origr); if (status_line != NULL) { text = apr_psprintf(r->pool, "%s %s\n", protocol, status_line); } else { text = apr_psprintf(r->pool, "%s %i\n", protocol, r->status); } sec_auditlog_write(msr, text, strlen(text)); /* Output headers */ /* No need to merge err_headers_out - Apache has already done that */ arr = apr_table_elts(r->headers_out); te = (apr_table_entry_t *)arr->elts; for (i = 0; i < arr->nelts; i++) { /*text = apr_psprintf(r->pool, "%s: %s\n", te[i].key, te[i].val);*/ text = apr_psprintf(r->pool, "%s: %s\n", log_escape_header_name(msr->r->pool, te[i].key), log_escape_nq(msr->r->pool, te[i].val)); sec_auditlog_write(msr, text, strlen(text)); } } } /* AUDITLOG_PART_RESPONSE_BODY */ if (strchr(msr->dcfg->auditlog_parts, AUDITLOG_PART_RESPONSE_BODY) != NULL) { char *text = NULL; if ((msr->ctx_out != NULL)&&(msr->ctx_out->buffer != NULL)) { text = apr_psprintf(r->pool, "\n--%s-E--\n", msr->new_auditlog_boundary); sec_auditlog_write(msr, text, strlen(text)); sec_auditlog_write(msr, msr->ctx_out->buffer, msr->ctx_out->bufused); wrote_response_body = 1; } } /* AUDITLOG_PART_TRAILER */ if (strchr(msr->dcfg->auditlog_parts, AUDITLOG_PART_TRAILER) != NULL) { apr_time_t now = apr_time_now(); char *t = NULL; text = apr_psprintf(r->pool, "\n--%s-H--\n", msr->new_auditlog_boundary); sec_auditlog_write(msr, text, strlen(text)); /* Messages */ for(i = 0; i < msr->messages->nelts; i++) { text = apr_psprintf(r->pool, "Message: %s\n", ((char **)msr->messages->elts)[i]); sec_auditlog_write(msr, text, strlen(text)); } /* Action */ t = (char *)apr_table_get(origr->headers_in, NOTE_ACTION); if (t != NULL) { text = apr_psprintf(r->pool, "Action: Intercepted (%s)\n", t); sec_auditlog_write(msr, text, strlen(text)); } /* Apache-Handler */ if (origr->handler != NULL) { text = apr_psprintf(r->pool, "Apache-Handler: %s\n", origr->handler); sec_auditlog_write(msr, text, strlen(text)); } /* Processing times */ if (msr->time_checkpoint_1 == 0) { text = apr_psprintf(r->pool, "Stopwatch: %" APR_TIME_T_FMT " %" APR_TIME_T_FMT " (- - -)\n", (msr->r->request_time), (now - msr->r->request_time)); } else { char str2[101] = "-"; char str3[101] = "-"; if (msr->time_checkpoint_2 != 0) { apr_snprintf(str2, sizeof(str2), "%" APR_TIME_T_FMT, (msr->time_checkpoint_2 - msr->r->request_time)); } if (msr->time_checkpoint_3 != 0) { apr_snprintf(str3, sizeof(str3), "%" APR_TIME_T_FMT, (msr->time_checkpoint_3 - msr->r->request_time)); } text = apr_psprintf(r->pool, "Stopwatch: %" APR_TIME_T_FMT " %" APR_TIME_T_FMT " (%" APR_TIME_T_FMT "%s %s %s)\n", (msr->r->request_time), (now - msr->r->request_time), (msr->time_checkpoint_1 - msr->r->request_time), ((msr->is_body_read == 0) ? "" : "*"), str2, str3 ); } sec_auditlog_write(msr, text, strlen(text)); /* Tell whomever is reading the response body was reconstructed */ if (reconstructed_request_body_flag) { text = apr_psprintf(r->pool, "Request-Body-Transformed: Reconstructed-URLEncoded\n"); sec_auditlog_write(msr, text, strlen(text)); } /* Our response body does not contain chunks */ if (wrote_response_body) { text = apr_psprintf(r->pool, "Response-Body-Transformed: Dechunked\n"); sec_auditlog_write(msr, text, strlen(text)); } /* Producer */ text = apr_psprintf(r->pool, "Producer: %s\n", MODULE_NAME_FULL); sec_auditlog_write(msr, text, strlen(text)); /* Server */ if (real_server_signature != NULL) { text = apr_psprintf(r->pool, "Server: %s\n", real_server_signature); sec_auditlog_write(msr, text, strlen(text)); } } /* AUDITLOG_PART_ENDMARKER */ text = apr_psprintf(r->pool, "\n--%s-Z--\n", msr->new_auditlog_boundary); sec_auditlog_write(msr, text, strlen(text)); apr_file_close(msr->new_auditlog_fd); /* Write an entry to the index file */ apr_md5_final(md5hash, &msr->new_auditlog_md5ctx); str2 = apr_psprintf(r->pool, "%s %i %i md5:%s", msr->new_auditlog_filename, 0, msr->new_auditlog_size, bytes2hex(r->pool, md5hash, 16)); if (str2 == NULL) return; /* If we are logging to a pipe we need to observe and * obey the pipe atomic write limit - PIPE_BUF. For * more details see the discussion in sec_guardian_logger, * above. */ if (dcfg->auditlog_name[0] == '|') { int limit; was_limited = 0; limit = PIPE_BUF - strlen(str2) - 5; if (limit <= 0) { sec_debug_log(r, 1, "Audit Log: Atomic PIPE write buffer too small: %i", PIPE_BUF); return; } str1 = construct_log_vcombinedus_limited(r, origr, limit, &was_limited); if (str1 == NULL) return; } else { str1 = construct_log_vcombinedus(r, origr); if (str1 == NULL) return; } if (was_limited == 0) text = apr_psprintf(r->pool, "%s %s\n", str1, str2); else text = apr_psprintf(r->pool, "%s %s L\n", str1, str2); if (text == NULL) return; nbytes = strlen(text); apr_file_write_full(dcfg->auditlog_fd, text, nbytes, &nbytes_written); } /** * Old-style (serial) audit logger. */ static int sec_audit_logger_serial(request_rec *r, request_rec *origr, sec_dir_config *dcfg, modsec_rec *msr) { char *the_request = origr->the_request; const char *status_line = NULL; const char *protocol = NULL; const char *error_notes = NULL; unsigned int o1size = 0, o2size = 0; char *o1 = NULL, *o2 = NULL; const apr_array_header_t *arr; apr_table_entry_t *te; apr_size_t nbytes = 0, nbytes_written; apr_status_t rv; char *vcombinedus, *t; int i = 0; sec_debug_log(r, 2, "sec_audit_logger_serial: start"); /* Return silently if we don't have a request line. This * means we will not be logging request timeouts. */ if (the_request == NULL) { sec_debug_log(r, 4, "sec_audit_logger_serial: skipping, the_request is null"); return DECLINED; } vcombinedus = construct_log_vcombinedus(r, origr); if (vcombinedus == NULL) return DECLINED; status_line = (r->status_line != NULL) ? r->status_line : ap_get_status_line(r->status); protocol = get_response_protocol(r); /* see if there is an error message stored in notes */ error_notes = (char *)apr_table_get(r->notes, "error-notes"); /* before allocating the first buffer, determine the size * of data; start with a reasonable number for the data we * ourselves produce, add the overhead, add the_request, * and input headers */ o1size = 1024; /* allow some space for the overhead */ o1size += strlen(vcombinedus); o1size += strlen(msr->new_auditlog_boundary); o1size += strlen(the_request) * 4; /* It can grow after it is escaped */ arr = apr_table_elts(r->headers_in); te = (apr_table_entry_t *)arr->elts; for (i = 0; i < arr->nelts; i++) { o1size += strlen(te[i].key); o1size += strlen(te[i].val); o1size += 5; } if (error_notes != NULL) o1size += strlen(error_notes) * 4; o1 = apr_palloc(r->pool, o1size + 1); if ((o1 == NULL)||(o1size + 1 == 0)) { sec_debug_log(r, 1, "sec_audit_logger: Could not allocate output buffer #1 [asked for %lu]", o1size + 1); return DECLINED; } strcpy(o1, "=="); strncat(o1, msr->new_auditlog_boundary, o1size - strlen(o1)); strncat(o1, "==============================\n", o1size - strlen(o1)); t = apr_psprintf(r->pool, "Request: %s\n", vcombinedus); strncat(o1, t, o1size - strlen(o1)); if (r->handler != NULL) { t = apr_psprintf(r->pool, "Handler: %s\n", log_escape_nq(r->pool, (char *)r->handler)); strncat(o1, t, o1size - strlen(o1)); } if (error_notes != NULL) { t = apr_psprintf(r->pool, "Error: %s\n", log_escape_nq(r->pool, (char *)error_notes)); strncat(o1, t, o1size - strlen(o1)); } strncat(o1, "----------------------------------------\n", o1size - strlen(o1)); /* request line */ t = apr_psprintf(r->pool, "%s\n", the_request); strncat(o1, t, o1size - strlen(o1)); /* input headers */ arr = apr_table_elts(r->headers_in); te = (apr_table_entry_t *)arr->elts; for (i = 0; i < arr->nelts; i++) { t = apr_psprintf(r->pool, "%s: %s\n", te[i].key, te[i].val); strncat(o1, t, o1size - strlen(o1)); } strncat(o1, "\n", o1size - strlen(o1)); /* determine the size of the second buffer */ o2size = 1024; o2size += strlen(msr->new_auditlog_boundary); if (status_line != NULL) o2size += strlen(status_line); else o2size += 10; arr = apr_table_elts(r->headers_out); te = (apr_table_entry_t *)arr->elts; for (i = 0; i < arr->nelts; i++) { o2size += strlen(te[i].key); o2size += strlen(te[i].val); o2size += 5; } o2 = apr_palloc(r->pool, o2size + 1); if ((o2 == NULL)||(o2size + 1 == 0)) { sec_debug_log(r, 1, "sec_audit_logger: Could not allocate output buffer #2 [asked for %lu]", o2size + 1); return DECLINED; } *o2 = '\0'; /* We don't log the headers when HTTP 0.9 is used */ if (!r->assbackwards) { if (status_line != NULL) { t = apr_psprintf(r->pool, "%s %s\n", protocol, status_line); } else { t = apr_psprintf(r->pool, "%s %i\n", protocol, r->status); } strncat(o2, t, o2size - strlen(o2)); /* output headers */ arr = apr_table_elts(r->headers_out); te = (apr_table_entry_t *)arr->elts; for (i = 0; i < arr->nelts; i++) { t = apr_psprintf(r->pool, "%s: %s\n", te[i].key, te[i].val); strncat(o2, t, o2size - strlen(o2)); } /* we do not need to be concerned with err_headers_out * at this point because they were already merged with * headers_out by now */ } /* The footer */ strncat(o2, "--", o2size - strlen(o2)); strncat(o2, msr->new_auditlog_boundary, o2size - strlen(o2)); strncat(o2, "--\n\n", o2size - strlen(o2)); /* Write to the file */ rv = apr_global_mutex_lock(modsec_auditlog_lock); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server, "mod_security: apr_global_mutex_lock(modsec_auditlog_lock) failed"); } nbytes = strlen(o1); apr_file_write_full(dcfg->auditlog_fd, o1, nbytes, &nbytes_written); { int body_action = 0; /* body not available by default */ char *message = NULL, *filename = NULL; sec_debug_log(r, 9, "sec_audit_logger_serial: is_relevant=%i, should_body_exist=%i, is_body_read=%i", msr->is_relevant, msr->should_body_exist, msr->is_body_read); /* determine what we need to do here */ if (msr->should_body_exist == 1) { if ((msr->is_body_read == 0)||(msr->ctx_in == NULL)) { body_action = 0; /* no payload (i.e. we did not read it) */ } else { if (msr->ctx_in->type != POST_IN_MEMORY) { msr->ctx_in->tmp_file_mode = REQBODY_FILE_LEAVE; body_action = 2; /* reference external files in the audit log */ /* Use only the base filename. If files * get moved around, absolute references * won't work. */ filename = strrchr(msr->ctx_in->tmp_file_name, '/'); if (filename == NULL) filename = msr->ctx_in->tmp_file_name; else filename = filename + 1; } else { body_action = 1; /* write from memory directly into a file */ } } } else { body_action = 3; /* do nothing (request had no payload) */ } /* now generate the message */ message = NULL; switch(body_action) { case 0 : message = "[POST payload not available]"; nbytes = strlen(message); break; case 1 : message = msr->ctx_in->buffer; nbytes = msr->ctx_in->buflen; break; case 2 : message = apr_psprintf(r->pool, "[@file:%s]", filename); nbytes = strlen(message); break; case 3 : default : /* do nothing */ break; } /* write the message */ if (message != NULL) { char *o3 = apr_psprintf(r->pool, "%lu\n", (unsigned long)nbytes); apr_size_t o3nbytes = strlen(o3); /* We first write out the size of the payload */ apr_file_write_full(dcfg->auditlog_fd, o3, o3nbytes, &nbytes_written); /* and then the payload itself */ apr_file_write_full(dcfg->auditlog_fd, message, nbytes, &nbytes_written); message = "\n\n"; nbytes = 2; apr_file_write_full(dcfg->auditlog_fd, message, nbytes, &nbytes_written); } } /* write the second part of the log */ nbytes = strlen(o2); apr_file_write_full(dcfg->auditlog_fd, o2, nbytes, &nbytes_written); rv = apr_global_mutex_unlock(modsec_auditlog_lock); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server, "mod_security: apr_global_mutex_unlock(modsec_auditlog_lock) failed"); } return OK; } static void sec_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 *fmt) { /* TODO this function gets called when something is written to the error log * if (r != NULL) sec_debug_log((request_rec *)r, 2, "HERE!"); */ } /** * Write the supplied data to the audit log (if the FD is ready), update * the size counters, update the hash context. */ int sec_auditlog_write(modsec_rec *msr, char *data, unsigned int len) { apr_size_t bytes_written, nbytes = len; apr_status_t rc; if ((msr->new_auditlog_fd == NULL)||(data == NULL)) return -1; rc = apr_file_write_full(msr->new_auditlog_fd, data, nbytes, &bytes_written); if (rc != APR_SUCCESS) { sec_debug_log(msr->r, 1, "Audit log: Failed writing (requested %ui bytes, written %ui)", nbytes, bytes_written); return -1; } /* Note the following will only take into account the actual * amount of bytes we've written. */ msr->new_auditlog_size += bytes_written; apr_md5_update(&msr->new_auditlog_md5ctx, data, bytes_written); return rc; } /** * Initialise new-style audit logging. */ void sec_auditlog_init(modsec_rec *msr) { request_rec *r = msr->r; char *uniqueid, *entry_filename, *entry_basename, *text; apr_status_t rc; const apr_array_header_t *arr = NULL; apr_table_entry_t *te = NULL; int i; /* the boundary is used by both audit log types */ msr->new_auditlog_boundary = create_auditlog_boundary(msr->r); /* Return silently if we don't have a request line. This * means we will not be logging request timeouts. */ if (msr->r->the_request == NULL) { sec_debug_log(r, 4, "Audit log initialisation: skipping, the_request is null"); return; } if ((msr->dcfg->auditlog_fd == NULL)||(msr->dcfg->auditlog_fd == NOT_SET_P)) { sec_debug_log(r, 4, "Audit log initialisation: skipping, auditlog_fd is null"); return; } if (msr->dcfg->auditlog_type == AUDITLOG_SERIAL) return; apr_md5_init(&msr->new_auditlog_md5ctx); uniqueid = get_env_var(msr->r, "UNIQUE_ID"); if (uniqueid == NULL) { sec_debug_log(r, 1, "Audit log: Concurrent audit logging requested, but UNIQUE_ID not found. Please activate mod_unique_id first."); return; } msr->new_auditlog_filename = construct_auditlog_filename(r, uniqueid); if (msr->new_auditlog_filename == NULL) return; if (msr->dcfg->auditlog_storage_dir == NULL) entry_filename = get_file_basename(r->pool, msr->dcfg->auditlog_name); else entry_filename = msr->dcfg->auditlog_storage_dir; if (entry_filename == NULL) return; entry_filename = apr_psprintf(msr->r->pool, "%s%s", entry_filename, msr->new_auditlog_filename); if (entry_filename == NULL) return; entry_basename = get_file_basename(r->pool, entry_filename); if (entry_basename == NULL) return; /* TODO OPTIMISE Surely it would be more efficient to check the folders for * the audit log repository base path in the configuration phase, to reduce * the work we do on every request. Also, since our path depends on time, * we could cache the time we last checked and don't check if we know * the folder is there. */ rc = apr_dir_make_recursive(entry_basename, CREATEMODE_DIR, r->pool); if (rc != APR_SUCCESS) { sec_debug_log(msr->r, 1, "Audit log: Failed to create subdirectories: %s (%s)", entry_basename, get_apr_error(r->pool, rc)); return; } rc = apr_file_open(&msr->new_auditlog_fd, entry_filename, APR_WRITE | APR_TRUNCATE | APR_CREATE | APR_BINARY, CREATEMODE, r->pool); if (rc != APR_SUCCESS) { sec_debug_log(msr->r, 1, "Audit log: Failed to create file: %s (%s)", entry_filename, get_apr_error(r->pool, rc)); return; } /* AUDITLOG_PART_HEADER */ text = apr_psprintf(r->pool, "--%s-A--\n", msr->new_auditlog_boundary); sec_auditlog_write(msr, text, strlen(text)); /* Format: time transaction_id remote_addr remote_port local_addr local_port */ text = apr_psprintf(r->pool, "[%s] %s %s %i %s %i", current_logtime(r), uniqueid, r->connection->remote_ip, r->connection->remote_addr->port, r->connection->local_ip, r->connection->local_addr->port); sec_auditlog_write(msr, text, strlen(text)); /* AUDITLOG_PART_REQUEST_HEADERS */ if (strchr(msr->dcfg->auditlog_parts, AUDITLOG_PART_REQUEST_HEADERS) != NULL) { text = apr_psprintf(r->pool, "\n--%s-B--\n", msr->new_auditlog_boundary); sec_auditlog_write(msr, text, strlen(text)); if (r->the_request != NULL) { sec_auditlog_write(msr, r->the_request, strlen(r->the_request)); sec_auditlog_write(msr, "\n", 1); } /* TODO Request headers can change after the body has been read * when chunked encoding is used on input. */ arr = apr_table_elts(r->headers_in); te = (apr_table_entry_t *)arr->elts; for (i = 0; i < arr->nelts; i++) { if (strncasecmp(te[i].key, "mod_security-", 13) != 0) { text = apr_psprintf(r->pool, "%s: %s\n", te[i].key, te[i].val); sec_auditlog_write(msr, text, strlen(text)); } } } } char *log_escape(apr_pool_t *p, char *text) { return _log_escape(p, text, 1, 0); } char *log_escape_nq(apr_pool_t *p, char *text) { return _log_escape(p, text, 0, 0); } char *log_escape_header_name(apr_pool_t *p, char *text) { return _log_escape(p, text, 0, 1); } /** * Transform input into a form safe for logging. */ char *_log_escape(apr_pool_t *p, char *text, int escape_quotes, int escape_colon) { const unsigned char *s = NULL; unsigned char *d = NULL; char *ret = NULL; if (text == NULL) return NULL; ret = apr_palloc(p, strlen(text) * 4 + 1); if (ret == NULL) return NULL; s = (const unsigned char *)text; d = (unsigned char *)ret; while(*s != 0) { switch(*s) { case ':' : if (escape_colon) { *d++ = '\\'; *d++ = ':'; } else { *d++ = *s; } break; case '"' : if (escape_quotes) { *d++ = '\\'; *d++ = '"'; } else { *d++ = *s; } break; case '\b' : *d++ = '\\'; *d++ = 'b'; break; case '\n' : *d++ = '\\'; *d++ = 'n'; break; case '\r' : *d++ = '\\'; *d++ = 'r'; break; case '\t' : *d++ = '\\'; *d++ = 't'; break; case '\v' : *d++ = '\\'; *d++ = 'v'; break; case '\\' : *d++ = '\\'; *d++ = '\\'; break; default : if ((*s <= 0x1f)||(*s >= 0x7f)) { *d++ = '\\'; *d++ = 'x'; c2x(*s, d); d += 2; } else { *d++ = *s; } break; } s++; } *d = 0; return ret; } /** * */ static void sec_debug_log(request_rec *r, int level, const char *text, ...) { sec_dir_config *dcfg = (sec_dir_config *)ap_get_module_config(r->per_dir_config, &security_module); va_list ap; char str1[1024] = ""; char str2[1256] = ""; apr_size_t nbytes, nbytes_written; apr_file_t *debuglog_fd = NULL; int filter_debug_level = 0; if (dcfg != NULL) { if ((dcfg->debuglog_fd != NULL)&&(dcfg->debuglog_fd != NOT_SET_P)) debuglog_fd = dcfg->debuglog_fd; if (dcfg->filter_debug_level != NOT_SET) filter_debug_level = dcfg->filter_debug_level; } /* Return immediately if we don't have where to write * or if the log level of the message is higher than * wanted in the log. */ if ((level != 1)&&( (debuglog_fd == NULL) || (level > filter_debug_level) )) return; va_start(ap, text); apr_vsnprintf(str1, sizeof(str1), text, ap); apr_snprintf(str2, sizeof(str2), "[%s] [%s/sid#%lx][rid#%lx][%s][%i] %s\n", current_logtime(r), ap_get_server_name(r), (unsigned long)(r->server), (unsigned long)r, ((r->uri == NULL) ? "" : log_escape_nq(r->pool, r->uri)), level, str1); if ((debuglog_fd != NULL)&&(level <= filter_debug_level)) { nbytes = strlen(str2); apr_file_write_full(debuglog_fd, str2, nbytes, &nbytes_written); } if (level == 1) { char *unique_id = (char *)get_env_var(r, "UNIQUE_ID"); char *hostname = (char *)r->hostname; if (unique_id != NULL) unique_id = apr_psprintf(r->pool, " [unique_id \"%s\"]", log_escape(r->pool, unique_id)); else unique_id = ""; if (hostname != NULL) hostname = apr_psprintf(r->pool, " [hostname \"%s\"]", log_escape(r->pool, hostname)); else hostname = ""; ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r->server, "[client %s] mod_security: %s%s [uri \"%s\"]%s", r->connection->remote_ip, str1, hostname, log_escape(r->pool, r->unparsed_uri), unique_id); } va_end(ap); return; } char *current_logtime(request_rec *r) { apr_time_exp_t t; char tstr[100]; apr_size_t len; apr_time_exp_lt(&t, apr_time_now()); apr_strftime(tstr, &len, 80, "%d/%b/%Y:%H:%M:%S ", &t); apr_snprintf(tstr + strlen(tstr), 80 - strlen(tstr), "%c%.2d%.2d", t.tm_gmtoff < 0 ? '-' : '+', t.tm_gmtoff / (60 * 60), t.tm_gmtoff % (60 * 60)); return apr_pstrdup(r->pool, tstr); } char *current_filetime(request_rec *r) { apr_time_exp_t t; char tstr[100]; apr_size_t len; apr_time_exp_lt(&t, apr_time_now()); apr_strftime(tstr, &len, 80, "%Y%m%d-%H%M%S", &t); return apr_pstrdup(r->pool, tstr); } /** * Constructs a filename that will be used to store an * audit log entry. */ static char *construct_auditlog_filename(request_rec *r, char *uniqueid) { apr_time_exp_t t; char tstr[300]; apr_size_t len; apr_time_exp_lt(&t, apr_time_now()); apr_strftime(tstr, &len, 299, "/%Y%m%d/%Y%m%d-%H%M/%Y%m%d-%H%M%S", &t); return apr_psprintf(r->pool, "%s-%s", tstr, uniqueid); } /* -- Multipart functions (PMULTIPART) ------------------------------------- */ int multipart_contains_files(multipart_data *mpd) { multipart_part **parts; int i, file_count = 0; parts = (multipart_part **)mpd->parts->elts; for(i = 0; i < mpd->parts->nelts; i++) { if ((parts[i]->type == MULTIPART_FILE) && (parts[i]->filename != NULL) && (strlen(parts[i]->filename) != 0)) file_count++; } return file_count; } multipart_part *multipart_get_part(multipart_data *mpd, char *name) { multipart_part **parts; int i; parts = (multipart_part **)mpd->parts->elts; for(i = 0; i < mpd->parts->nelts; i++) { if (strcasecmp(parts[i]->name, name) == 0) return parts[i]; } return NULL; } int multipart_check_files_names(modsec_rec *msr, signature *sig, variable *var) { multipart_data *mpd = msr->mpd; multipart_part **parts; int i, rs; parts = (multipart_part **)mpd->parts->elts; for(i = 0; i < mpd->parts->nelts; i++) { if ((parts[i]->type == MULTIPART_FILE)&&(parts[i]->filename != NULL)) { sec_debug_log(msr->r, 4, "Checking signature \"%s\" at FILES_NAMES(%s)", log_escape(msr->r->pool, sig->pattern), log_escape(msr->r->pool, parts[i]->name)); rs = check_sig_against_string(msr, sig, parts[i]->filename, VAR_FILES_NAMES, parts[i]->name); if (rs != OK) return rs; } } return OK; } int multipart_check_files_sizes(modsec_rec *msr, signature *sig, variable *var) { multipart_data *mpd = msr->mpd; multipart_part **parts; int i, rs; parts = (multipart_part **)mpd->parts->elts; for(i = 0; i < mpd->parts->nelts; i++) { if ((parts[i]->type == MULTIPART_FILE)&&(parts[i]->filename != NULL)) { char *size_string = apr_psprintf(msr->r->pool, "%u", parts[i]->tmp_file_size); sec_debug_log(msr->r, 4, "Checking signature \"%s\" at FILES_SIZES(\"%s\")", log_escape(msr->r->pool, sig->pattern), log_escape(msr->r->pool, parts[i]->name)); rs = check_sig_against_string(msr, sig, size_string, VAR_FILES_SIZES, parts[i]->name); if (rs != OK) return rs; } } return OK; } char *construct_put_filename(modsec_rec *msr) { char c, *put_file_name = NULL; char *tmp_dir = NULL, *t = NULL; put_file_name = apr_pstrdup(msr->r->pool, msr->r->uri); t = strstr(put_file_name, "?"); if (t != NULL) *t = 0; t = strrchr(put_file_name, '/'); if (t != NULL) put_file_name = t + 1; /* allow letters, digits and dots, nuke the rest */ t = put_file_name; while((c = *t) != 0) { if (!( isalnum(c)||(c == '.') )) *t = '_'; t++; } if (msr->dcfg->upload_dir != NULL) tmp_dir = msr->dcfg->upload_dir; else tmp_dir = get_temp_folder(msr->r->pool); return apr_psprintf(msr->r->pool, "%s/%s-%s-%s", tmp_dir, current_filetime(msr->r), msr->r->connection->remote_ip, put_file_name); } apr_status_t request_body_file_cleanup(void *data) { modsec_rec *msr = (modsec_rec *)data; char *put_filename = NULL; if (msr == NULL) return -1; sec_debug_log(msr->r, 4, "request_body_file_cleanup: Started"); /* only continue if there is a temporary file */ if ((msr->ctx_in == NULL)||(msr->ctx_in->tmp_file_name == NULL)) return -1; if (msr->ctx_in->is_put) { put_filename = construct_put_filename(msr); } /* The new-style audit log makes no use of the temporary * request body; in such cases we don't have to copy it, just rename it. * But this function does not need to think about it because mode will * be REQBODY_FILE_DELETE. */ if (msr->ctx_in->tmp_file_mode == REQBODY_FILE_LEAVE) { /* if it's a PUT request and the files need to be kept * then we need to copy this file into another one */ if ((msr->ctx_in->is_put)&&(msr->dcfg->upload_keep_files)) { sec_debug_log(msr->r, 4, "request_body_file_cleanup: Copying request body file %s to %s", msr->ctx_in->tmp_file_name, put_filename); if (sec_copy_file(msr->ctx_in->tmp_file_name, put_filename) < 0) { sec_debug_log(msr->r, 1, "request_body_file_cleanup: Failed to copy %s to %s", msr->ctx_in->tmp_file_name, put_filename); } } return 1; } else { /* if it's a PUT request and the files need to be kept * then we should rename this file and return */ if ((msr->ctx_in->is_put)&&(msr->dcfg->upload_keep_files)) { sec_debug_log(msr->r, 4, "request_body_file_cleanup: Renaming request body file %s to %s", msr->ctx_in->tmp_file_name, put_filename); if (apr_file_rename(msr->ctx_in->tmp_file_name, put_filename, msr->r->pool) != APR_SUCCESS) { sec_debug_log(msr->r, 1, "request_body_file_cleanup: Failed to rename %s to %s", msr->ctx_in->tmp_file_name, put_filename); } return 1; } } /* Poor file, nobody wants you */ if (unlink(msr->ctx_in->tmp_file_name) < 0) { sec_debug_log(msr->r, 1, "request_body_file_cleanup: Failed to delete file \"%s\" because %d(\"%s\")", log_escape(msr->r->pool, msr->ctx_in->tmp_file_name), errno, log_escape(msr->r->pool, strerror(errno))); } else { sec_debug_log(msr->r, 2, "request_body_file_cleanup: Deleted file \"%s\"", log_escape(msr->r->pool, msr->ctx_in->tmp_file_name)); } return 1; } apr_status_t multipart_cleanup(void *data) { multipart_data *mpd = (multipart_data *)data; if (data == NULL) return -1; sec_debug_log(mpd->r, 4, "multipart_cleanup: Started"); /* loop through the list of parts * and delete the temporary files, but only if * file storage was not requested, or if storage * of relevant files was requested and this isn't * such a request */ if ( (mpd->dcfg->upload_keep_files == KEEP_FILES_OFF) || ((mpd->dcfg->upload_keep_files == KEEP_FILES_RELEVANT_ONLY) && (mpd->msr->is_relevant <= 0)) ) { multipart_part **parts; int i; parts = (multipart_part **)mpd->parts->elts; for(i = 0; i < mpd->parts->nelts; i++) { if (parts[i]->type == MULTIPART_FILE) { if (parts[i]->tmp_file_name != NULL) { sec_debug_log(mpd->r, 4, "multipart_cleanup: deleting temporary file (part) \"%s\"", log_escape(mpd->r->pool, parts[i]->tmp_file_name)); if (unlink(parts[i]->tmp_file_name) < 0) { sec_debug_log(mpd->r, 1, "multipart_cleanup: Failed to delete file (part) \"%s\" because %d(%s)", log_escape(mpd->r->pool, parts[i]->tmp_file_name), errno, strerror(errno)); } else { sec_debug_log(mpd->r, 2, "multipart_cleanup: Deleted file (part) \"%s\"", log_escape(mpd->r->pool, parts[i]->tmp_file_name)); } } } } } else { /* delete empty files only */ multipart_part **parts; int i; parts = (multipart_part **)mpd->parts->elts; for(i = 0; i < mpd->parts->nelts; i++) { if ((parts[i]->type == MULTIPART_FILE)&&(parts[i]->tmp_file_size == 0)) { if (parts[i]->tmp_file_name != NULL) { sec_debug_log(mpd->r, 4, "multipart_cleanup: deleting empty temporary file (part) \"%s\"", log_escape(mpd->r->pool, parts[i]->tmp_file_name)); if (unlink(parts[i]->tmp_file_name) < 0) { sec_debug_log(mpd->r, 1, "multipart_cleanup: Failed to delete empty file (part) \"%s\" because %d(%s)", log_escape(mpd->r->pool, parts[i]->tmp_file_name), errno, strerror(errno)); } else { sec_debug_log(mpd->r, 2, "multipart_cleanup: Deleted empty file (part) \"%s\"", log_escape(mpd->r->pool, parts[i]->tmp_file_name)); } } } } } return 1; } int multipart_init(multipart_data *mpd, modsec_rec *msr, char **error_msg) { request_rec *r = msr->r; char *content_type; if (error_msg == NULL) return -1; *error_msg = NULL; mpd->dcfg = msr->dcfg; mpd->p = r->pool; mpd->msr = msr; mpd->r = msr->r; content_type = (char *)apr_table_get(r->headers_in, "Content-Type"); if (content_type == NULL) { *error_msg = apr_psprintf(r->pool, "multipart_init: Content-Type header not available"); return -1; } mpd->boundary = strstr(content_type, "boundary="); if ((mpd->boundary != NULL)&&(*(mpd->boundary + 9) != 0)) mpd->boundary = mpd->boundary + 9; else { *error_msg = apr_psprintf(r->pool, "multipart_init: Boundary not found or invalid"); return -1; } mpd->parts = apr_array_make(mpd->p, 10, sizeof(multipart_part *)); mpd->bufleft = MULTIPART_BUF_SIZE; mpd->bufptr = mpd->buf; mpd->buf_contains_line = 1; mpd->mpp = NULL; /* schedule resource cleanup for later */ apr_pool_cleanup_register(r->pool, (void *)mpd, multipart_cleanup, apr_pool_cleanup_null); return 1; } int multipart_process_chunk(multipart_data *mpd, const char *buf, unsigned int size, char **error_msg) { char *inptr = (char *)buf; unsigned int inleft = size; if (error_msg == NULL) return -1; *error_msg = NULL; if (size == 0) return 1; if (mpd->seen_data == 0) mpd->seen_data = 1; if (mpd->is_complete) { sec_debug_log(mpd->r, 4, "Multipart: ignoring data after last boundary (received %i bytes)", size); return 1; } if (mpd->bufleft == 0) { *error_msg = apr_psprintf(mpd->r->pool, "Multipart: internal error in process_chunk: no more space in the buffer"); return -1; } /* here we loop through the data available, byte by byte */ while(inleft > 0) { char c = *inptr; int process_buffer = 0; if ((c == 0x0d)&&(mpd->bufleft == 1)) { /* we don't want to take 0x0d as the last byte in the buffer */ process_buffer = 1; } else { inptr++; inleft = inleft - 1; *(mpd->bufptr) = c; mpd->bufptr++; mpd->bufleft--; } /* until we either reach the end of the line * or the end of our internal buffer */ if ((c == 0x0a)||(mpd->bufleft == 0)||(process_buffer)) { *(mpd->bufptr) = 0; /* boundary preconditions: length of the line greater than * the length of the boundary + the first two characters * are dashes "-" */ if ( mpd->buf_contains_line && (strlen(mpd->buf) > strlen(mpd->boundary) + 2) && (((*(mpd->buf) == '-'))&&(*(mpd->buf + 1) == '-')) && (strncmp(mpd->buf + 2, mpd->boundary, strlen(mpd->boundary)) == 0) ) { char *boundary_end = mpd->buf + 2 + strlen(mpd->boundary); if ( (*boundary_end == '\r') &&(*(boundary_end + 1) == '\n') &&(*(boundary_end + 2) == '\0') ) { /* simple boundary */ if (multipart_process_boundary(mpd, 0, error_msg) < 0) return -1; } else if ( (*boundary_end == '-') &&(*(boundary_end + 1) == '-') &&(*(boundary_end + 2) == '\r') &&(*(boundary_end + 3) == '\n') &&(*(boundary_end + 4) == '\0') ) { /* final boundary */ mpd->is_complete = 1; if (multipart_process_boundary(mpd, 1, error_msg) < 0) return -1; } else { /* error */ *error_msg = apr_psprintf(mpd->r->pool, "Multipart: invalid boundary encountered: %s", log_escape_nq(mpd->r->pool, mpd->buf)); return -1; } } else { if (mpd->mpp == NULL) { sec_debug_log(mpd->r, 4, "Multipart: ignoring data before first boundary"); } else { if (mpd->mpp_state == 0) { if ((mpd->bufleft == 0)||(process_buffer)) { /* part header lines must be shorter than * MULTIPART_BUF_SIZE bytes */ *error_msg = apr_psprintf(mpd->r->pool, "Multipart: part header line over %i bytes long", MULTIPART_BUF_SIZE); return -1; } if (multipart_process_part_header(mpd, error_msg) < 0) return -1; } else { if (multipart_process_part_data(mpd, error_msg) < 0) return -1; } } } /* reset the pointer to the beginning of the buffer * and continue to accept input data */ mpd->bufptr = mpd->buf; mpd->bufleft = MULTIPART_BUF_SIZE; mpd->buf_contains_line = (c == 0x0a) ? 1 : 0; } if ((mpd->is_complete)&&(inleft != 0)) { sec_debug_log(mpd->r, 4, "Multipart: ignoring data after last boundary (%i bytes left)", inleft); return 1; } } return 1; } char *multipart_construct_filename(multipart_data *mpd) { char c, *p, *q = mpd->mpp->filename; char *filename; /* find the last backward slash and consider the * filename to be only what's right from it */ p = strrchr(q, '\\'); if (p != NULL) q = p + 1; /* do the same for the forward slash */ p = strrchr(q, '/'); if (p != NULL) q = p + 1; /* allow letters, digits and dots, replace * everything else with underscoresa */ p = filename = apr_pstrdup(mpd->p, q); while((c = *p) != 0) { if (!( isalnum(c)||(c == '.') )) *p = '_'; p++; } return filename; } int multipart_parse_content_disposition(multipart_data *mpd, char *value) { char *p = NULL, *t = NULL; /* accept only what we understand */ if (strncmp(value, "form-data", 9) != 0) { return -1; } /* see if there are any other parts to parse */ p = value + 9; while((*p == '\t')||(*p == ' ')) p++; if (*p == '\0') return 1; /* this is OK */ if (*p != ';') return -2; p++; /* parse the appended parts */ while(*p != '\0') { char *name = NULL, *value = NULL, *start = NULL; /* go over the whitespace */ while((*p == '\t')||(*p == ' ')) p++; if (*p == '\0') return -3; start = p; while((*p != '\0')&&(*p != '=')&&(*p != '\t')&&(*p != ' ')) p++; if (*p == '\0') return -4; name = apr_pstrmemdup(mpd->r->pool, start, (p - start)); while((*p == '\t')||(*p == ' ')) p++; if (*p == '\0') return -5; if (*p != '=') return -13; p++; while((*p == '\t')||(*p == ' ')) p++; if (*p == '\0') return -6; if (*p == '"') { /* quoted */ p++; if (*p == '\0') return -7; start = p; value = apr_pstrdup(mpd->r->pool, p); t = value; while(*p != '\0') { if (*p == '\\') { if (*(p + 1) == '\0') { /* improper escaping */ return -8; } /* only " and \ can be escaped */ if ((*(p + 1) == '"')||(*(p + 1) == '\\')) { p++; } else { /* improper escaping */ /* We allow for now because IE sends * improperly escaped content and there's * nothing we can do about it. * * return -9; */ } } else if (*p == '"') { *t = '\0'; break; } *t++ = *p++; } if (*p == '\0') return -10; p++; /* go over the quote at the end */ } else { /* not quoted */ start = p; while((*p != '\0')&&(is_token_char(*p))) p++; value = apr_pstrmemdup(mpd->r->pool, start, (p - start)); } /* evaluate part */ if (strcmp(name, "name") == 0) { if (mpd->mpp->name != NULL) return -14; mpd->mpp->name = value; sec_debug_log(mpd->r, 9, "multipart_parse_content_disposition: name %s", log_escape_nq(mpd->r->pool, value)); } else if (strcmp(name, "filename") == 0) { if (mpd->mpp->filename != NULL) return -15; mpd->mpp->filename = value; sec_debug_log(mpd->r, 9, "multipart_parse_content_disposition: filename %s", log_escape_nq(mpd->r->pool, value)); } else return -11; if (*p != '\0') { while((*p == '\t')||(*p == ' ')) p++; /* the next character must be a zero or a semi-colon */ if (*p == '\0') return 1; /* this is OK */ if (*p != ';') return -12; p++; /* move over the semi-colon */ } /* loop will stop when (*p == '\0') */ } return 1; } int multipart_process_part_header(multipart_data *mpd, char **error_msg) { int rc; if (error_msg == NULL) return -1; *error_msg = NULL; if ((mpd->buf[0] == '\r')&&(mpd->buf[1] == '\n')&&(mpd->buf[2] == '\0')) { char *header_value; /* empty line */ header_value = (char *)apr_table_get(mpd->mpp->headers, "Content-Disposition"); if (header_value == NULL) { *error_msg = apr_psprintf(mpd->r->pool, "Multipart: part is missing the Content-Disposition header"); return -1; } rc = multipart_parse_content_disposition(mpd, header_value); if (rc < 0) { *error_msg = apr_psprintf(mpd->r->pool, "Multipart: invalid Content-Disposition header (%i): %s", rc, log_escape_nq(mpd->r->pool, header_value)); return -1; } if (mpd->mpp->name == NULL) { *error_msg = apr_psprintf(mpd->r->pool, "Multipart: part name missing"); return -1; } if (mpd->mpp->filename != NULL) { mpd->mpp->type = MULTIPART_FILE; } else { mpd->mpp->type = MULTIPART_FORMDATA; } mpd->mpp_state = 1; mpd->mpp->last_header_name = NULL; } else { /* header line */ if ((mpd->buf[0] == '\t')||(mpd->buf[0] == ' ')) { char *header_value, *new_value, *data; /* header folding, add data to the header we are building */ if (mpd->mpp->last_header_name == NULL) { /* we are not building a header at this moment */ *error_msg = apr_psprintf(mpd->r->pool, "Multipart: invalid part header (invalid folding)"); return -1; } /* locate the beginning of the data */ data = mpd->buf; while((*data == '\t')||(*data == ' ')) data++; new_value = apr_pstrdup(mpd->r->pool, data); sec_remove_lf_crlf_inplace(new_value); /* update the header value in the table */ header_value = (char *)apr_table_get(mpd->mpp->headers, mpd->mpp->last_header_name); new_value = apr_pstrcat(mpd->r->pool, header_value, " ", new_value, NULL); apr_table_set(mpd->mpp->headers, mpd->mpp->last_header_name, new_value); sec_debug_log(mpd->r, 9, "multipart_process_par_header: continued folder header \"%s\" with \"%s\"", log_escape(mpd->r->pool, mpd->mpp->last_header_name), log_escape(mpd->r->pool, data)); if (strlen(new_value) > 4096) { *error_msg = apr_psprintf(mpd->r->pool, "Multpart: invalid part header (too long)"); return -1; } } else { char *header_name, *header_value, *data; /* new header */ data = mpd->buf; while((*data != ':')&&(*data != '\0')) data++; if (*data == '\0') { *error_msg = apr_psprintf(mpd->r->pool, "Multipart: invalid part header (missing colon): %s", log_escape_nq(mpd->r->pool, mpd->buf)); return -1; } header_name = apr_pstrmemdup(mpd->r->pool, mpd->buf, (data - mpd->buf)); /* extract the value value */ data++; while((*data == '\t')||(*data == ' ')) data++; header_value = apr_pstrdup(mpd->r->pool, data); sec_remove_lf_crlf_inplace(header_value); /* error if the name already exists */ if (apr_table_get(mpd->mpp->headers, header_name) != NULL) { *error_msg = apr_psprintf(mpd->r->pool, "Multipart: part header already exists: %s", log_escape_nq(mpd->r->pool, header_name)); return -1; } apr_table_setn(mpd->mpp->headers, header_name, header_value); mpd->mpp->last_header_name = header_name; sec_debug_log(mpd->r, 9, "multipart_process_par_header: added part header \"%s\" \"%s\"", log_escape(mpd->r->pool, header_name), log_escape(mpd->r->pool, header_value)); } } return 1; } int multipart_process_part_data(multipart_data *mpd, char **error_msg) { char *p = mpd->buf + (MULTIPART_BUF_SIZE - mpd->bufleft) - 2; char localreserve[2] = { '\0', '\0' }; /* initialized to quiet warning */ int bytes_reserved = 0; if (error_msg == NULL) return -1; *error_msg = NULL; /* preserve the last two bytes for later */ if (MULTIPART_BUF_SIZE - mpd->bufleft >= 2) { bytes_reserved = 1; localreserve[0] = *p; localreserve[1] = *(p + 1); mpd->bufleft += 2; *p = 0; } /* add data to the part we are building */ if (mpd->mpp->type == MULTIPART_FILE) { /* only store individual files on disk if we are going * to keep them or if we need to have them approved later */ if ((mpd->dcfg->upload_approve_script != NULL)||(mpd->dcfg->upload_keep_files > 0)) { /* first create a temporary file if we don't have it already */ if (mpd->mpp->tmp_file_fd == 0) { char *filename = multipart_construct_filename(mpd); /* the temp folder must be chosen in the configuration * create the filename first */ if (mpd->dcfg->upload_dir != NULL) { mpd->mpp->tmp_file_name = apr_psprintf(mpd->p, "%s/%s-%s-%s", mpd->dcfg->upload_dir, current_filetime(mpd->r), mpd->r->connection->remote_ip, filename); } else { mpd->mpp->tmp_file_name = apr_psprintf(mpd->p, "%s/%s-%s-%s", get_temp_folder(mpd->r->pool), current_filetime(mpd->r), mpd->r->connection->remote_ip, filename); } if ((mpd->mpp->tmp_file_fd = open(mpd->mpp->tmp_file_name, O_WRONLY | O_APPEND | O_CREAT | O_BINARY, CREATEMODE_UNISTD)) == -1) { /* we've failed while opening the page, so we'll try * again with a more unique filename */ mpd->mpp->tmp_file_name = apr_pstrcat(mpd->p, mpd->mpp->tmp_file_name, "_XXXXXX", NULL); mpd->mpp->tmp_file_fd = sec_mkstemp(mpd->mpp->tmp_file_name); } /* do we have an opened file? */ if (mpd->mpp->tmp_file_fd < 0) { *error_msg = apr_psprintf(mpd->r->pool, "Multipart: Failed to create file \"%s\"", log_escape(mpd->r->pool, mpd->mpp->tmp_file_name)); return -1; } sec_debug_log(mpd->r, 2, "Multipart: Created file \"%s\"", log_escape(mpd->r->pool, mpd->mpp->tmp_file_name)); } /* write the reserve first */ if (mpd->reserve[0] == 1) { if (write(mpd->mpp->tmp_file_fd, &mpd->reserve[1], 2) != 2) { *error_msg = apr_psprintf(mpd->r->pool, "Multipart: writing to \"%s\" failed", log_escape(mpd->r->pool, mpd->mpp->tmp_file_name)); return -1; } mpd->mpp->tmp_file_size += 2; } /* write data to the file */ if (write(mpd->mpp->tmp_file_fd, mpd->buf, MULTIPART_BUF_SIZE - mpd->bufleft) != (MULTIPART_BUF_SIZE - mpd->bufleft)) { *error_msg = apr_psprintf(mpd->r->pool, "Multipart: writing to \"%s\" failed", log_escape(mpd->r->pool, mpd->mpp->tmp_file_name)); return -1; } mpd->mpp->tmp_file_size += (MULTIPART_BUF_SIZE - mpd->bufleft); } else { /* just keep track of the file size */ if (mpd->reserve[0] == 1) mpd->mpp->tmp_file_size += 2; mpd->mpp->tmp_file_size += (MULTIPART_BUF_SIZE - mpd->bufleft); } } else if (mpd->mpp->type == MULTIPART_FORMDATA) { char *value_part = NULL; /* add this part to the list of parts */ if (mpd->reserve[0] == 1) { value_part = apr_pstrcat(mpd->p, &(mpd->reserve[1]), mpd->buf, NULL); } else { value_part = apr_pstrdup(mpd->p, mpd->buf); } *(char **)apr_array_push(mpd->mpp->value_parts) = value_part; sec_debug_log(mpd->r, 9, "Added data to variable: %s", log_escape_nq(mpd->r->pool, value_part)); } else { *error_msg = apr_psprintf(mpd->r->pool, "Multipart: unknown part type %i", mpd->mpp->type); return -1; } /* store the reserved bytes to the multipart * context so that they don't get lost */ if (bytes_reserved) { mpd->reserve[0] = 1; mpd->reserve[1] = localreserve[0]; mpd->reserve[2] = localreserve[1]; } else { mpd->reserve[0] = 0; } return 1; } int multipart_complete(multipart_data *mpd, char **error_log) { if ((mpd->seen_data != 0)&&(mpd->is_complete == 0)) { *error_log = apr_psprintf(mpd->r->pool, "Multipart: final boundary missing"); return -1; } return 1; } int multipart_process_boundary(multipart_data *mpd, int last_part, char **error_log) { sec_debug_log(mpd->r, 4, "multipart_process_boundary: last_part = %i", last_part); /* if there was a part being built finish it */ if (mpd->mpp != NULL) { /* close the temp file */ if ((mpd->mpp->type == MULTIPART_FILE)&&(mpd->mpp->tmp_file_name != NULL)&&(mpd->mpp->tmp_file_fd != 0)) { close(mpd->mpp->tmp_file_fd); } if (mpd->mpp->type != MULTIPART_FILE) { /* now construct a single string out of the parts */ mpd->mpp->value = apr_array_pstrcat(mpd->r->pool, mpd->mpp->value_parts, 0); if (mpd->mpp->value == NULL) return -1; } /* add the part to the list of parts */ *(multipart_part **)apr_array_push(mpd->parts) = mpd->mpp; if (mpd->mpp->type == MULTIPART_FILE) sec_debug_log(mpd->r, 9, "multipart_process_boundary: added file part %x to the list: name \"%s\" file name \"%s\" size %u", mpd->mpp, log_escape(mpd->r->pool, mpd->mpp->name), log_escape(mpd->r->pool, mpd->mpp->filename), mpd->mpp->tmp_file_size); else sec_debug_log(mpd->r, 9, "multipart_process_boundary: added part %x to the list: name \"%s\"", mpd->mpp, log_escape(mpd->r->pool, mpd->mpp->name)); mpd->mpp = NULL; } if (last_part == 0) { /* start building a new part */ mpd->mpp = (multipart_part *)apr_pcalloc(mpd->p, sizeof(multipart_part)); mpd->mpp->type = MULTIPART_FORMDATA; mpd->mpp_state = 0; mpd->mpp->headers = apr_table_make(mpd->r->pool, 10); mpd->mpp->last_header_name = NULL; mpd->reserve[0] = 0; mpd->reserve[1] = 0; mpd->reserve[2] = 0; mpd->reserve[3] = 0; mpd->mpp->value_parts = apr_array_make(mpd->r->pool, 10, sizeof(char *)); } return 1; } int verify_uploaded_file(request_rec *r, char *file_path, char *approver_script, char **error_msg) { char *script_output = NULL; char **argv; if (error_msg == NULL) return -1; *error_msg = NULL; argv = apr_pcalloc(r->pool, 3 * sizeof(char *)); argv[0] = approver_script; argv[1] = file_path; argv[2] = NULL; sec_debug_log(r, 4, "verify_uploaded_file: Executing %s to validate %s", approver_script, file_path); if ((sec_exec_child(approver_script, (const char**)argv, r, &script_output) != APR_SUCCESS)||(script_output == NULL)) { *error_msg = apr_psprintf(r->pool, "verify_uploaded_file: Execution of the approver script \"%s\" failed", log_escape(r->pool, approver_script)); return 0; } if (script_output == NULL) { *error_msg = apr_psprintf(r->pool, "verify_uploaded_file: The approver script \"%s\" output is NULL", log_escape(r->pool, approver_script)); return 0; } sec_debug_log(r, 2, "Approver script said: %s", script_output); if (script_output[0] != '1') { *error_msg = apr_psprintf(r->pool, "File \"%s\" rejected by the approver script \"%s\"", log_escape(r->pool, file_path), log_escape(r->pool, approver_script)); return 0; } return 1; } int multipart_verify_uploaded_files(request_rec *r, multipart_data *mpd, char *approver_script, char **error_msg) { multipart_part **parts; int i; if (error_msg == NULL) return -1; *error_msg = NULL; parts = (multipart_part **)mpd->parts->elts; for(i = 0; i < mpd->parts->nelts; i++) { if (parts[i]->type == MULTIPART_FILE) { if (verify_uploaded_file(r, parts[i]->tmp_file_name, approver_script, error_msg) != 1) return 0; } } return 1; } char *multipart_reconstruct_urlencoded_body(multipart_data *mpd) { multipart_part **parts; char *body; unsigned int body_len; int i; if (mpd == NULL) return NULL; /* calculate buffer size */ body_len = 1; parts = (multipart_part **)mpd->parts->elts; for(i = 0; i < mpd->parts->nelts; i++) { if (parts[i]->type == MULTIPART_FORMDATA) { body_len += 4; body_len += strlen(parts[i]->name) * 3; body_len += strlen(parts[i]->value) * 3; } else { body_len += 6; body_len += strlen(parts[i]->filename) * 3; body_len += 20; } } /* allocate the buffer */ body = apr_palloc(mpd->r->pool, body_len + 1); if ((body == NULL)||(body_len + 1 == 0)) return NULL; *body = 0; parts = (multipart_part **)mpd->parts->elts; for(i = 0; i < mpd->parts->nelts; i++) { char buf[20]; if (*body != 0) { strncat(body, "&", body_len - strlen(body)); } if (parts[i]->type == MULTIPART_FORMDATA) { strnurlencat(body, parts[i]->name, body_len - strlen(body)); strncat(body, "=", body_len - strlen(body)); strnurlencat(body, parts[i]->value, body_len - strlen(body)); } else { strncat(body, "FILE+", body_len - strlen(body)); strnurlencat(body, parts[i]->filename, body_len - strlen(body)); strncat(body, "+", body_len - strlen(body)); apr_snprintf(buf, 19, "%u", parts[i]->tmp_file_size); strncat(body, buf, body_len - strlen(body)); } } return body; } int multipart_get_variables(multipart_data *mpd, apr_table_t *parsed_args, sec_dir_config *dcfg, char **error_msg) { multipart_part **parts; char *my_error_msg = NULL; int i; if (error_msg == NULL) return -1; *error_msg = NULL; parts = (multipart_part **)mpd->parts->elts; for(i = 0; i < mpd->parts->nelts; i++) { if (parts[i]->type == MULTIPART_FORMDATA) { char *name = NULL, *value = NULL; name = normalise_relaxed(mpd->r, dcfg, parts[i]->name, &my_error_msg); if (name == NULL) { *error_msg = apr_psprintf(mpd->r->pool, "Error normalising parameter name: %s", my_error_msg); return -1; } value = normalise_relaxed(mpd->r, dcfg, parts[i]->value, &my_error_msg); if (value == NULL) { *error_msg = apr_psprintf(mpd->r->pool, "Error normalising parameter value: %s", my_error_msg); return -1; } apr_table_add(parsed_args, name, value); } } return 1; } /* -- Core (PCORE) -------------------------------------------------------- */ int sec_initialise(modsec_rec *msr) { char *my_error_msg = NULL; request_rec *r = msr->r; apr_status_t rc; const apr_array_header_t *arr; apr_table_entry_t *te; int i; msr->request_uri = normalise(r, msr->dcfg, msr->r->unparsed_uri, &my_error_msg); if (msr->request_uri == NULL) { msr->tmp_message = apr_psprintf(r->pool, "Error normalising REQUEST_URI: %s", my_error_msg); return perform_action(msr, msr->dcfg->actionset, NULL); } sec_debug_log(r, 4, "Normalised REQUEST_URI: \"%s\"", log_escape(r->pool, msr->request_uri)); sec_debug_log(r, 2, "Parsing arguments..."); /* parse and validate GET parameters when available */ if (r->args != NULL) { if (parse_arguments(r->args, msr->parsed_args, r, msr->dcfg, &my_error_msg) < 0) { msr->tmp_message = apr_psprintf(r->pool, "Invalid parameters: %s", my_error_msg); return perform_action(msr, msr->dcfg->actionset, NULL); } } /* validate headers */ arr = apr_table_elts(r->headers_in); te = (apr_table_entry_t *)arr->elts; for (i = 0; i < arr->nelts; i++) { int check_unicode_encoding = msr->dcfg->check_unicode_encoding; char *header_value = NULL; if (strncasecmp(te[i].key, "mod_security-", 13) == 0) { msr->tmp_message= apr_psprintf(r->pool, "mod_security: Internal header detected in request: %s", te[i].key); return perform_action(msr, msr->dcfg->actionset, NULL); } /* We need to turn-off the unicode encoding checks for the * Referer header because it sometimes contains the information * from another web site, and we can't be sure the information * will be a valid Unicode text. */ if (strcasecmp(te[i].key, "Referer") == 0) msr->dcfg->check_unicode_encoding = 0; if (normalise_relaxed(r, msr->dcfg, te[i].key, &my_error_msg) == NULL) { msr->dcfg->check_unicode_encoding = check_unicode_encoding; msr->tmp_message = apr_psprintf(r->pool, "Error validating header name: %s", my_error_msg); return perform_action(msr, msr->dcfg->actionset, NULL); } header_value = normalise_relaxed(r, msr->dcfg, te[i].val, &my_error_msg); if (header_value == NULL) { msr->dcfg->check_unicode_encoding = check_unicode_encoding; msr->tmp_message = apr_psprintf(r->pool, "Error validating header value (%s): %s", te[i].key, my_error_msg); return perform_action(msr, msr->dcfg->actionset, NULL); } msr->dcfg->check_unicode_encoding = check_unicode_encoding; /* cache the normalised header value for later */ apr_table_add(msr->cache_headers_in, te[i].key, header_value); } /* parse, optionally validate cookies */ if (parse_cookies(msr, &my_error_msg) < 0) { msr->tmp_message = apr_psprintf(r->pool, "Error parsing cookies: %s", my_error_msg); return perform_action(msr, msr->dcfg->actionset, NULL); } { char *content_type = (char *)apr_table_get(r->headers_in, "Content-Type"); if (content_type != NULL) sec_debug_log(r, 3, "Content-Type is \"%s\"", log_escape(r->pool, content_type)); else sec_debug_log(r, 3, "Content-Type is not available"); rc = read_post_payload(msr); if (rc < 0) { /* the error message prepared by read_post_payload */ return perform_action(msr, msr->dcfg->actionset, NULL); } /* go back straight away if there is no body */ if (!msr->is_body_read) { return DECLINED; } if (msr->r->method_number == M_PUT) { sec_debug_log(r, 2, "PUT method detected, request body is file: %s", msr->ctx_in->tmp_file_name); /* run the approval script on the file */ if ((msr->dcfg->upload_approve_script != NULL)&&(msr->ctx_in != NULL)&&(msr->ctx_in->tmp_file_name != NULL)) { if (verify_uploaded_file(r, msr->ctx_in->tmp_file_name, msr->dcfg->upload_approve_script, &my_error_msg) != 1) { msr->tmp_message = apr_psprintf(r->pool, "Error verifying file: %s", my_error_msg); return perform_action(msr, msr->dcfg->actionset, NULL); } } } else if ((content_type != NULL) && (strncasecmp(content_type, "application/x-www-form-urlencoded", 33) == 0) && (msr->r->method_number == M_POST)) { int j; /* Check that the byte range is ok */ sec_debug_log(r, 3, "Checking byte range in POST payload"); for (j = 0; j < msr->ctx_in->buflen; j++) { int c = ((unsigned char *)msr->ctx_in->buffer)[j]; if ((c < msr->dcfg->range_start) || (c > msr->dcfg->range_end)) { msr->tmp_message = apr_psprintf(r->pool, "Invalid character detected in POST payload [%i]", c); return perform_action(msr, msr->dcfg->actionset, NULL); } } /* parse post payload */ if (msr->_post_payload != NULL) { sec_debug_log(r, 3, "Parsing variables from POST payload"); if (parse_arguments(msr->_post_payload, msr->parsed_args, r, msr->dcfg, &my_error_msg) < 0) { msr->tmp_message = apr_psprintf(r->pool, "Error parsing POST parameters: %s", my_error_msg); return perform_action(msr, msr->dcfg->actionset, NULL); } msr->_post_payload = normalise(r, msr->dcfg, msr->_post_payload, &my_error_msg); if (msr->_post_payload == NULL) { msr->tmp_message = apr_psprintf(r->pool, "Error normalising POST payload: %s", my_error_msg); return perform_action(msr, msr->dcfg->actionset, NULL); } } } else if ((content_type != NULL) && (msr->r->method_number == M_POST) && (strncasecmp(content_type, "multipart/form-data", 19) == 0) && (msr->mpd != NULL) ) { /* get parsed variables */ if (multipart_get_variables(msr->mpd, msr->parsed_args, msr->dcfg, &my_error_msg) < 0) { msr->tmp_message = apr_psprintf(r->pool, "Error parsing multipart parameters: %s", my_error_msg); return perform_action(msr, msr->dcfg->actionset, NULL); } if (msr->dcfg->upload_approve_script != NULL) { /* we only accept 1 as a correct result; the function may also return * 0 for verification failed and -1 for an error (e.g. the script cannot * be executed) */ if (multipart_verify_uploaded_files(r, msr->mpd, msr->dcfg->upload_approve_script, &my_error_msg) != 1) { msr->tmp_message = apr_psprintf(r->pool, "Error verifying files: %s", my_error_msg); return perform_action(msr, msr->dcfg->actionset, NULL); } } } else if (msr->_post_payload != NULL) { msr->_post_payload = remove_binary_content(r, msr->_post_payload, msr->_post_len); if (msr->_post_payload == NULL) { msr->tmp_message = apr_psprintf(r->pool, "Error while removing binary content from POST"); return perform_action(msr, msr->dcfg->actionset, NULL); } } } return DECLINED; } void sec_time_checkpoint(modsec_rec *msr, int checkpoint_no) { char note[100], note_name[100]; apr_time_t now; now = apr_time_now(); switch(checkpoint_no) { case 1 : msr->time_checkpoint_1 = now; break; case 2 : msr->time_checkpoint_2 = now; break; case 3 : msr->time_checkpoint_3 = now; break; default : sec_debug_log(msr->r, 1, "sec_time_checkpoint: Unknown checkpoint: %i", checkpoint_no); return; break; } apr_snprintf(note, 99, "%" APR_TIME_T_FMT, (now - msr->r->request_time)); apr_snprintf(note_name, 99, NOTE_TIME "%i", checkpoint_no); apr_table_set(msr->r->notes, note_name, note); sec_debug_log(msr->r, 4, "Time #%i: %" APR_TIME_T_FMT " usec", checkpoint_no, (now - msr->r->request_time)); } /* Create a per-request modsecurity context. We * need this context even if we are not going * to do anything. */ modsec_rec *sec_create_context(request_rec *r) { sec_srv_config *scfg = (sec_srv_config *)ap_get_module_config(r->server->module_config, &security_module); sec_dir_config *dcfg = (sec_dir_config *)ap_get_module_config(r->per_dir_config, &security_module); modsec_rec *msr = NULL; char *content_length = NULL; msr = (modsec_rec *)apr_pcalloc(r->pool, sizeof(*msr)); msr->r = r; msr->scfg = scfg; /* We will make a copy of the per-dir configuration. * WARNING this is a shallow copy, meaning most pointers * will remain pointing to the common locations. This * is OK for now as we are not changing any of the * data we aren't copying. */ msr->dcfg = apr_pcalloc(r->pool, sizeof(sec_dir_config)); memcpy(msr->dcfg, dcfg, sizeof(sec_dir_config)); /* Make a deeper copy of the actionset. Still * not a 100% deep copy but deep enough for what * we change on the per-request basis. */ if ((dcfg->actionset != NULL)&&(dcfg->actionset != NOT_SET_P)) { msr->dcfg->actionset = apr_pcalloc(r->pool, sizeof(actionset_t)); memcpy(msr->dcfg->actionset, dcfg->actionset, sizeof(actionset_t)); } sec_set_dir_defaults(msr->dcfg); msr->request_uri = NULL; msr->_post_payload = NULL; msr->parsed_args = apr_table_make(r->pool, 10); msr->parsed_cookies = apr_table_make(r->pool, 10); msr->is_relevant = 0; msr->is_dynamic = NOT_SET; msr->explicit_auditlog = NOT_SET; msr->messages = apr_array_make(r->pool, 10, sizeof(char *)); msr->cache_request_uri = NULL; msr->cache_path_info = NULL; msr->cache_the_request = NULL; msr->cache_query_string = NULL; msr->cache_request_basename = NULL; msr->cache_script_basename = NULL; msr->cache_headers_in = apr_table_make(r->pool, 10); content_length = (char *)apr_table_get(r->headers_in, "Content-Length"); if (content_length == NULL) { /* there's no C-L, could be chunked? */ char *transfer_encoding = (char *)apr_table_get(r->headers_in, "Transfer-Encoding"); if ((transfer_encoding != NULL)&&(strstr(transfer_encoding, "chunked") != NULL)) { msr->should_body_exist = 1; } else { /* no C-L, no chunked */ msr->should_body_exist = 0; } } else { /* C-L found */ msr->should_body_exist = 1; } /* Store context for others to find */ store_msr(r, msr); return msr; } #ifdef ENABLE_EARLY_HOOK static int sec_check_access_early(request_rec *r) { sec_debug_log(r, 2, "Early processing activated"); return sec_check_access(r); } #endif int sec_check_access(request_rec *r) { char *env_modsec_enable = NULL; modsec_rec *msr = NULL; int real_action, real_status; int rc, filter_engine; sec_debug_log(r, 2, "Detection phase starting (request %x): \"%s\"", r, ((r->the_request == NULL) ? "" : log_escape(r->pool, r->the_request))); msr = find_msr(r); /* We only continue if this is a main request */ if (!ap_is_initial_req(r)) { sec_debug_log(r, 2, "sec_check_access: Filtering off, not an initial request"); if ((msr != NULL)&&(msr->ctx_in != NULL)) { ap_filter_t *f = r->input_filters; int already_there = 0; while(f != NULL) { if (f->ctx == msr->ctx_in) { already_there = 1; break; } f = f->next; } if (already_there == 0) { sec_debug_log(r, 2, "sec_check_access: Added sec_filter_in filter (ctx %x)", msr->ctx_in); ap_add_input_filter_handle(global_sec_filter_in, msr->ctx_in, r, r->connection); } } return DECLINED; } #ifdef ENABLE_EARLY_HOOK /* If msr exists that means we already run in a previous hook */ if (msr != NULL) { sec_debug_log(r, 4, "sec_check_access: Ignoring request that was already processed"); return DECLINED; } #endif msr = sec_create_context(r); if (msr->dcfg == NULL) { sec_debug_log(r, 2, "sec_check_access: Filtering off, dcfg is null"); return DECLINED; } filter_engine = msr->dcfg->filter_engine; env_modsec_enable = (char *)get_env_var(r, "MODSEC_ENABLE"); if (env_modsec_enable != NULL) { sec_debug_log(r, 4, "sec_check_access: Detected MODSEC_ENABLE: %s", env_modsec_enable); if (strcasecmp(env_modsec_enable, "Off") == 0) filter_engine = FILTERING_OFF; else if (strcasecmp(env_modsec_enable, "On") == 0) filter_engine = FILTERING_ON; else if (strcasecmp(env_modsec_enable, "DynamicOnly") == 0) filter_engine = FILTERING_DYNAMIC_ONLY; else { sec_debug_log(r, 1, "Ignoring invalid MODSEC_ENABLE: %s", env_modsec_enable); } } /* refuse to work if the filtering is off */ if (filter_engine == FILTERING_OFF) { sec_debug_log(r, 2, "sec_check_access: Filtering off, not enabled here"); return DECLINED; } if (r->handler != NULL) msr->is_dynamic = 1; else { if (filter_engine == FILTERING_DYNAMIC_ONLY) { request_rec *subreq = NULL; sec_debug_log(r, 4, "Executing a subrequest to determine request handler"); /* OK, so we are to examine the request only if it is dynamic, but at * this point it looks like it isn't. To make sure we need to construct * a subrequest to ask Apache how it would process it. */ subreq = ap_sub_req_lookup_uri(r->uri, r, NULL); sec_debug_log(r, 9, "Subrequest says %s for %s", subreq->handler, r->uri); if (subreq->handler != NULL) msr->is_dynamic = 1; else msr->is_dynamic = 0; ap_destroy_sub_req(subreq); if (msr->is_dynamic == 0) { sec_debug_log(r, 2, "sec_check_access: Filtering off, disabled for non-dynamic requests (and this is one)"); return DECLINED; } } } /* this variable should be used in the subsequent phases to * determine whether to run or not without having to duplicate * the complex logic */ msr->is_enabled = 1; /* Initialize mod_security structures for this request. * As of 1.8.6 non-fatal default actions are not allowed * during the initialization phase. */ real_action = msr->dcfg->actionset->action; real_status = msr->dcfg->actionset->status; if (msr->dcfg->actionset->action == ACTION_NONE) { msr->dcfg->actionset->action = ACTION_DENY; } if (msr->dcfg->actionset->status == 0) { msr->dcfg->actionset->status = HTTP_FORBIDDEN; } rc = sec_initialise(msr); msr->dcfg->actionset->action = real_action; msr->dcfg->actionset->status = real_status; sec_time_checkpoint(msr, 1); /* Process rules only if there were no errors * in the initialization stage. */ if (rc == DECLINED) { rc = sec_check_all_signatures(msr); } /* Make a note for the logger */ if (rc != DECLINED) { char *note = apr_psprintf(r->pool, "%i", rc); apr_table_setn(r->headers_in, NOTE_ACTION, note); apr_table_setn(r->subprocess_env, NOTE_ACTED, "1"); } else { apr_table_unset(r->headers_in, NOTE_ACTION); } /* Export the request body as a note */ if (msr->is_body_read) { char *post_payload = msr->_post_payload; if (msr->mpd != NULL) { if (msr->_fake_post_payload != NULL) post_payload = msr->_fake_post_payload; else post_payload = construct_fake_urlencoded(msr, msr->parsed_args); } if (post_payload != NULL) apr_table_setn(r->notes, NOTE_BODY, post_payload); } sec_time_checkpoint(msr, 2); return rc; } static apr_status_t locks_remove(void *data) { if (modsec_auditlog_lock != NULL) { apr_global_mutex_destroy(modsec_auditlog_lock); modsec_auditlog_lock = NULL; } return 0; } static int sec_init(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) { sec_srv_config *scfg = (sec_srv_config *)ap_get_module_config(s->module_config, &security_module); apr_status_t rv; void *data = NULL; int first_time = 0; apr_pool_userdata_get(&data, "sec_init_flag", s->process->pool); if (data == NULL) { first_time = 1; apr_pool_userdata_set((const void *)1, "sec_init_flag", apr_pool_cleanup_null, s->process->pool); } if ((scfg->server_response_token)&&(!first_time)) { ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, 0, s, "mod_security: SecServerResponseToken directive is deprecated"); } /* Although this may seem not to make any sense we are * in fact allocating sufficient space in the signature * so we can change it later by using brute force. */ real_server_signature = apr_pstrdup(p, ap_get_server_version()); if (scfg->server_signature != NULL) { ap_add_version_component(p, scfg->server_signature); change_server_signature(s, scfg); } if ((rv = apr_global_mutex_create(&modsec_auditlog_lock, NULL, APR_LOCK_DEFAULT, p)) != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, "mod_security: Could not create modsec_auditlog_lock"); return HTTP_INTERNAL_SERVER_ERROR; } #ifdef __SET_MUTEX_PERMS rv = unixd_set_global_mutex_perms(modsec_auditlog_lock); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, "mod_security: Could not set permissions on modsec_auditlog_lock; check User and Group directives"); return HTTP_INTERNAL_SERVER_ERROR; } #endif #if !(defined(WIN32) || defined(NETWARE)) if (scfg->chroot_dir != NULL) { if (first_time == 0) { ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0, s, "mod_security: chroot checkpoint #2 (pid=%i ppid=%i)", getpid(), getppid()); if (chdir(scfg->chroot_dir) < 0) { ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, s, "mod_security: chroot failed, unable to chdir to %s, errno=%d(%s)", scfg->chroot_dir, errno, strerror(errno)); exit(1); } if (chroot(scfg->chroot_dir) < 0) { ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, s, "mod_security: chroot failed, path=%s, errno=%d(%s)", scfg->chroot_dir, errno, strerror(errno)); exit(1); } if (chdir("/") < 0) { ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, s, "mod_security: chdoot failed, unable to chdir to /, errno=%d(%s)", errno, strerror(errno)); exit(1); } ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0, s, "mod_security: chroot successful, path=%s", scfg->chroot_dir); scfg->chroot_completed = 1; } else { ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0, s, "mod_security: chroot checkpoint #1 (pid=%i ppid=%i)", getpid(), getppid()); } } #endif apr_pool_cleanup_register(p, (void *)s, locks_remove, apr_pool_cleanup_null); if (first_time) { if (scfg->server_signature != NULL) { ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0, s, "mod_security/%s configured - %s", MODULE_RELEASE, real_server_signature); } else { ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0, s, "mod_security/%s configured", MODULE_RELEASE); } } return OK; } static void sec_child_init(apr_pool_t *pool, server_rec *s) { sec_srv_config *scfg = (sec_srv_config *)ap_get_module_config(s->module_config, &security_module); apr_status_t rs; if (modsec_auditlog_lock != NULL) { rs = apr_global_mutex_child_init(&modsec_auditlog_lock, NULL, pool); if (rs != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, rs, s, "Failed to child-init auditlog mutex"); } } #if !(defined(WIN32)||defined(NETWARE)) /* Refuse to work if chroot was requested but * not performed. Unfortunately, we can't perform * this check earlier, or at a better location. */ if ((scfg->chroot_dir != NULL)&&(scfg->chroot_completed == 0)) { ap_log_error(APLOG_MARK, APLOG_EMERG | APLOG_NOERRNO, 0, s, "mod_security: Internal chroot facility mailfunctioned! Exiting."); /* This is ugly but better than running a server * without a chroot when a chroot was configured. * We sleep a little (one sec) to prevent children * from dying too fast. */ apr_sleep(1000000); exit(1); } #endif change_server_signature(s, scfg); /* initialise each child process with a different seed */ srand(time(NULL) * getpid()); } void send_error_bucket(ap_filter_t *f, int status) { apr_bucket_brigade *brigade; apr_bucket *bucket; brigade = apr_brigade_create(f->r->pool, f->r->connection->bucket_alloc); bucket = ap_bucket_error_create(status, NULL, f->r->pool, f->r->connection->bucket_alloc); APR_BRIGADE_INSERT_TAIL(brigade, bucket); bucket = apr_bucket_eos_create(f->r->connection->bucket_alloc); APR_BRIGADE_INSERT_TAIL(brigade, bucket); ap_pass_brigade(f->next, brigade); } /* The purpose of this input filter is to break the * filter chain in those cases where we have already * read the POST data into our buffer. So, instead * of getting the data from the next filter in the * chain we serve it from our buffer. */ static apr_status_t sec_filter_in(ap_filter_t *f, apr_bucket_brigade *pbbOut, ap_input_mode_t eMode, apr_read_type_e eBlock, apr_off_t nBytes) { request_rec *r = f->r; conn_rec *c = r->connection; sec_filter_in_ctx *ctx = NULL; sec_debug_log(r, 4, "sec_filter_in: start: inputmode=%i, readtype=%i, nBytes=%i", eMode, eBlock, nBytes); /* the context will always be supplied to us */ if (!(ctx = f->ctx)) { sec_debug_log(r, 1, "sec_filter_in: context not found!"); return ap_get_brigade(f->next, pbbOut, eMode, eBlock, nBytes); } /* Skip the call if there isn't any work left for us */ if (ctx->done_writing == 1) { return ap_get_brigade(f->next, pbbOut, eMode, eBlock, nBytes); } if (ctx->type == POST_ON_DISK) { /* our data is stored on disk, here we create a small * buffer and open the file for reading */ if (ctx->tmp_file_fd <= 0) { ctx->buffer = ctx->output_ptr = apr_palloc(r->pool, 4000); if (ctx->buffer == NULL) { sec_debug_log(r, 1, "sec_filter_in: Failed to allocate 4K bytes"); return ap_get_brigade(f->next, pbbOut, eMode, eBlock, nBytes); } sec_debug_log(r, 4, "ctx->tmp_file_name \"%s\"", log_escape(r->pool, ctx->tmp_file_name)); ctx->tmp_file_fd = open(ctx->tmp_file_name, O_RDONLY | O_BINARY); if (ctx->tmp_file_fd < 0) { sec_debug_log(r, 1, "sec_filter_in: Failed to open file \"%s\"", log_escape(r->pool, ctx->tmp_file_name)); return ap_get_brigade(f->next, pbbOut, eMode, eBlock, nBytes); } } } /* Send a chunk of data further down the filter chain */ if (ctx->output_sent < ctx->sofar) { apr_bucket *pbktOut; unsigned int len = 4000; /* We send 4000 bytes out in a go unless a smaller * size was requested by the caller. But we never * send more than 4000 bytes. */ if (len > nBytes) len = (unsigned int)nBytes; /* we have fewer than $len bytes left */ if (ctx->sofar - ctx->output_sent < len) len = ctx->sofar - ctx->output_sent; if (ctx->type == POST_ON_DISK) { int gotlen; /* read a chunk of the file into the buffer */ gotlen = read(ctx->tmp_file_fd, ctx->output_ptr, len); if (gotlen <= 0) { sec_debug_log(r, 1, "sec_filter_in: Failed to read %i bytes from the tmp file [fd=%i, gotlen=%i, errno=%i (%s)]", len, ctx->tmp_file_fd, gotlen, errno, strerror(errno)); return ap_get_brigade(f->next, pbbOut, eMode, eBlock, nBytes); } else { len = gotlen; } /* the third parameter, NULL, means "make a copy of the data" */ pbktOut = apr_bucket_heap_create(ctx->output_ptr, len, NULL, c->bucket_alloc); /* we don't increase ctx->output_ptr here because * we are always using the same buffer */ ctx->output_sent += len; } else { /* TODO can we lower memory consumption by using the same data * below, by supplying a non-NULL third parameter? */ pbktOut = apr_bucket_heap_create(ctx->output_ptr, len, NULL, c->bucket_alloc); ctx->output_ptr += len; ctx->output_sent += len; } APR_BRIGADE_INSERT_TAIL(pbbOut, pbktOut); sec_debug_log(r, 4, "sec_filter_in: Sent %d bytes (%lu total)", len, ctx->output_sent); } /* are we done yet? */ if (ctx->output_sent == ctx->sofar) { /* send an EOS bucket, we're done */ apr_bucket *pbktOut = apr_bucket_eos_create(c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(pbbOut, pbktOut); sec_debug_log(r, 4, "sec_filter_in: Sent EOS bucket"); ctx->done_writing = 1; /* nothing left for us to do in this request */ ap_remove_input_filter(f); if (ctx->type == POST_ON_DISK) { close(ctx->tmp_file_fd); } } return APR_SUCCESS; } static apr_status_t sec_filter_out(ap_filter_t *f, apr_bucket_brigade *pbbIn) { request_rec *r = f->r; sec_filter_out_ctx *ctx = NULL; conn_rec *c = r->connection; apr_bucket *pbktIn; sec_debug_log(r, 3, "sec_filter_out: start"); /* create a context if one does not already exist */ if (!(ctx = f->ctx)) { modsec_rec *msr = find_msr(r); const char *s; if (msr == NULL) { sec_debug_log(r, 1, "sec_filter_out: Unable to find msr"); ap_remove_output_filter(f); return ap_pass_brigade(f->next, pbbIn); } f->ctx = ctx = apr_pcalloc(r->pool, sizeof(*ctx)); if (ctx == NULL) { sec_debug_log(r, 1, "sec_filter_out: Unable to allocate %i bytes", sizeof(*ctx)); ap_remove_output_filter(f); return ap_pass_brigade(f->next, pbbIn); } ctx->msr = msr; msr->ctx_out = ctx; sec_debug_log(r, 3, "sec_filter_out: Content-Type = \"%s\"", log_escape(r->pool, (char *)r->content_type)); /* if scan_output_mimetypes is not null that means output * filtering should be selective, using the content type value */ if (ctx->msr->dcfg->scan_output_mimetypes != NULL) { char *content_type; /* check for the case when the content type is not set */ if (r->content_type == NULL) { if (strstr(ctx->msr->dcfg->scan_output_mimetypes, " (null) ") == NULL) { ap_remove_output_filter(f); return ap_pass_brigade(f->next, pbbIn); } } else { char *p = NULL; /* in all other cases, see if the content type appears * on the acceptable mime types list */ content_type = apr_psprintf(r->pool, " %s ", r->content_type); if (content_type == NULL) { sec_debug_log(r, 1, "sec_filter_out: Unable to allocate %i bytes", strlen(r->content_type) + 2); ap_remove_output_filter(f); return ap_pass_brigade(f->next, pbbIn); } strtolower(content_type); /* Hide the character encoding information * if present. Sometimes the content type header * looks like this "text/html; charset=xyz" ... */ p = strstr(content_type, ";"); if (p != NULL) { *p = ' '; *(p + 1) = 0; } if (strstr(ctx->msr->dcfg->scan_output_mimetypes, content_type) == NULL) { ap_remove_output_filter(f); return ap_pass_brigade(f->next, pbbIn); } } } ctx->buflen = 0; /* look up the Content-Length header to see if we know * the amount of data coming our way */ s = apr_table_get(r->headers_out, "Content-Length"); /* try this too, mod_cgi seems to put headers there */ if (s == NULL) s = apr_table_get(r->err_headers_out, "Content-Length"); if (s != NULL) { long len = strtol(s, NULL, 10); if (len == 0) { sec_debug_log(r, 2, "sec_filter_out: Skipping since Content-Length is zero."); ap_remove_output_filter(f); return ap_pass_brigade(f->next, pbbIn); } if ((len < 0)||(len + 1 <= 0)) { sec_debug_log(r, 1, "sec_filter_out: Invalid Content-Length: %li", len); ap_remove_output_filter(f); return ap_pass_brigade(f->next, pbbIn); } sec_debug_log(r, 3, "sec_filter_out: got Content-Length %li", len); ctx->buflen = len; if (ctx->buflen >= 1073741824) { /* refuse to work past this size */ ap_remove_output_filter(f); return ap_pass_brigade(f->next, pbbIn); } } /* use the default buffer length if everything else fails */ if (ctx->buflen <= 0) { ctx->buflen = 16384; } ctx->buffer = apr_palloc(f->r->pool, ctx->buflen + 1); if ((ctx->buffer == NULL)||(ctx->buflen + 1 == 0)) { sec_debug_log(r, 1, "sec_filter_out: Failed to allocate buffer [%li]", ctx->buflen + 1); ap_remove_output_filter(f); return ap_pass_brigade(f->next, pbbIn); } ctx->input_ptr = ctx->buffer; } /* read data into our buffer */ while(!APR_BRIGADE_EMPTY(pbbIn)) { const char *data; apr_size_t len; pbktIn = APR_BRIGADE_FIRST(pbbIn); if (AP_BUCKET_IS_ERROR(pbktIn)) { sec_debug_log(r, 2, "sec_filter_out: Got error bucket, abandoning output filtering!"); ap_remove_output_filter(f); return ap_pass_brigade(f->next, pbbIn); } else if (APR_BUCKET_IS_FLUSH(pbktIn)) { /* do nothing */ } else if(APR_BUCKET_IS_EOS(pbktIn)) { sec_debug_log(r, 3, "sec_filter_out: done reading"); ctx->buffer[ctx->bufused] = 0; ctx->done_reading = 1; } else { apr_status_t rv; rv = apr_bucket_read(pbktIn, &data, &len, APR_BLOCK_READ); if (rv != APR_SUCCESS) { sec_debug_log(r, 1, "sec_filter_out: apr_bucket_read failed with %i", rv); ap_remove_output_filter(f); return ap_pass_brigade(f->next, pbbIn); } sec_debug_log(r, 3, "sec_filter_out: got %u bytes, bufused=%u, buflen=%u", len, ctx->bufused, ctx->buflen); if (ctx->bufused + len > ctx->buflen) { char *newbuffer; unsigned long int newsize = ctx->buflen * 2; if (ctx->buflen >= 1073741824) { /* refuse to work past this size */ ap_remove_output_filter(f); return ap_pass_brigade(f->next, pbbIn); } /* increase the size of the new buffer until the data fits */ while(ctx->bufused + len >= newsize) newsize = newsize * 2; sec_debug_log(r, 3, "sec_filter_out: expanding buffer to %lu", newsize); /* allocate a larger buffer */ newbuffer = apr_palloc(f->r->pool, newsize + 1); if ((newbuffer == NULL)||(newsize + 1 == 0)) { sec_debug_log(r, 1, "sec_filter_out: Failed to allocate buffer [%lu]", newsize + 1); ap_remove_output_filter(f); return ap_pass_brigade(f->next, pbbIn); } memcpy(newbuffer, ctx->buffer, ctx->bufused); ctx->buffer = newbuffer; ctx->buflen = newsize; ctx->input_ptr = ctx->buffer + ctx->bufused; } memcpy(ctx->input_ptr, data, len); ctx->input_ptr += len; ctx->bufused += len; } apr_bucket_delete(pbktIn); } /* we wait patiently (for the next call) until we get * all of the output */ if (ctx->done_reading == 0) return APR_SUCCESS; /* this shouldn't happen but doesn't hurt us to check */ if (ctx->done_writing == 1) { sec_debug_log(r, 1, "sec_filter_out: internal error, we shouldn't have arrived here"); return ap_pass_brigade(f->next, pbbIn); } ctx->msr->output_filter_complete = 1; /* check the output */ { signature **signatures; int i, j, rv = HTTP_FORBIDDEN, var_type = VAR_UNKNOWN; signatures = (signature **)ctx->msr->dcfg->signatures->elts; for (i = 0; i < ctx->msr->dcfg->signatures->nelts; i++) { variable **variables; /* do not process signatures that are not proper signatures */ if (signatures[i]->is_inheritance_placeholder != 0) continue; /* ignore non-output filters */ if (signatures[i]->is_output != PHASE_OUTPUT) continue; /* Retrieve the variable type from the signature. Only * one variable per signature is supported for output * filtering at this time. */ variables = (variable **)signatures[i]->variables->elts; for (j = 0; j < signatures[i]->variables->nelts; j++) { var_type = variables[j]->type; } switch(var_type) { case VAR_OUTPUT : sec_debug_log(r, 2, "Checking signature \"%s\" at OUTPUT", log_escape(r->pool, signatures[i]->pattern)); rv = check_sig_against_string(ctx->msr, signatures[i], ctx->buffer, VAR_OUTPUT, NULL); break; case VAR_OUTPUT_STATUS : sec_debug_log(r, 2, "Checking signature \"%s\" at OUTPUT_STATUS", log_escape(r->pool, signatures[i]->pattern)); rv = check_sig_against_string(ctx->msr, signatures[i], apr_psprintf(r->pool, "%i", r->status), VAR_OUTPUT_STATUS, NULL); break; default : sec_debug_log(r, 1, "Error: Unknown variable type for output filtering (%i)", var_type); break; } if ((rv != OK)&&(rv != DECLINED)) { #ifdef DEFLATE_HACK /* Apparently, this is not needed any more as it appears to be working just fine now. */ /* dirty hack, but filtering doesn't work with mod_deflate * (only when we are rejecting a requests, works fine otherwise) * so we remove it from the filter list; we only do this if * we need to interrupt the request */ { ap_filter_t *f = r->output_filters; while(f != NULL) { if (strcasecmp(f->frec->name, "deflate") == 0) { ap_remove_output_filter(f); sec_debug_log(r, 2, "sec_filter_out: Removed deflate from the output_filters list"); break; } f = f->next; } } #endif /* it seems that mod_proxy sets the status line * and it later overrides the real status * in ap_send_error_response; so we kill it here */ r->status_line = NULL; send_error_bucket(f, rv); ctx->done_writing = 1; sec_time_checkpoint(ctx->msr, 3); sec_debug_log(r, 9, "sec_filter_out: Responded with %i", rv); return APR_SUCCESS; /* the error was sent through the bucket */ } /* rc will be declined if "allow" is used */ if (rv == DECLINED) break; } } sec_time_checkpoint(ctx->msr, 3); /* if we're that means that all went well and that * we now need to send the output to the filter chain */ ctx->output_ptr = ctx->buffer; ctx->output_sent = 0; while(ctx->output_sent < ctx->bufused) { apr_status_t rc; apr_bucket_brigade *pbbTmp; apr_bucket *pbktTmp; unsigned int batch = 4000; /* adjust the chunk size */ if (ctx->bufused - ctx->output_sent < batch) batch = ctx->bufused - ctx->output_sent; pbbTmp = apr_brigade_create(r->pool, c->bucket_alloc); if (pbbTmp == NULL) { ctx->done_writing = 1; ap_remove_output_filter(f); return APR_EGENERAL; } /* TODO again, we are allocating memory for data that we already have * in memory - we should just create a single bucket out of all the * data we have. */ pbktTmp = apr_bucket_heap_create(ctx->output_ptr, batch, NULL, c->bucket_alloc); if (pbktTmp == NULL) { ctx->done_writing = 1; ap_remove_output_filter(f); return APR_EGENERAL; } APR_BRIGADE_INSERT_TAIL(pbbTmp, pbktTmp); ctx->output_ptr += batch; ctx->output_sent += batch; sec_debug_log(r, 3, "sec_filter_out: sent %i bytes", batch); rc = ap_pass_brigade(f->next, pbbTmp); if (rc != APR_SUCCESS) { ctx->done_writing = 1; ap_remove_output_filter(f); return rc; } } /* TODO don't we have a function for this already */ /* send the EOS bucket */ { apr_bucket_brigade *pbbTmp; apr_bucket *pbktTmp; pbbTmp = apr_brigade_create(r->pool, c->bucket_alloc); pbktTmp = apr_bucket_eos_create(f->r->connection->bucket_alloc); APR_BRIGADE_INSERT_TAIL(pbbTmp, pbktTmp); ap_pass_brigade(f->next, pbbTmp); sec_debug_log(r, 3, "sec_filter_out: done writing"); } ctx->done_writing = 1; ap_remove_output_filter(f); return APR_SUCCESS; } static void sec_insert_filter(request_rec *r) { modsec_rec *msr = NULL; sec_debug_log(r, 9, "sec_insert_filter: Starting"); msr = find_msr(r); if (msr == NULL) { sec_debug_log(r, 2, "sec_insert_filter: Skipping, msr is NULL (INTERNAL ERROR)"); return; } if (msr->is_enabled != 1) { sec_debug_log(r, 2, "sec_insert_filter: Skipping, is_enabled is false"); return; } if (msr->output_filter_complete != 0) { sec_debug_log(r, 2, "sec_insert_filter: Skipping, output filtering already completed"); return; } if (msr->dcfg->scan_output == 0) { sec_debug_log(r, 2, "sec_insert_filter: Skipping, output filtering is off here"); return; } sec_debug_log(r, 2, "scan_pre: Adding output filter"); ap_add_output_filter_handle(global_sec_filter_out, msr->ctx_out, r, r->connection); } static void register_hooks(apr_pool_t *p) { static const char *init_before_list[] = { "mod_unique_id.c", "mod_ssl.c", NULL }; static const char *init_after_list[] = { "mod_fcgid.c", "mod_cgid.c", NULL }; ap_hook_post_config(sec_init, init_before_list, init_after_list, APR_HOOK_REALLY_LAST); ap_hook_log_transaction(sec_logger, NULL, NULL, APR_HOOK_MIDDLE); #ifdef ENABLE_EARLY_HOOK ap_hook_post_read_request(sec_check_access_early, NULL, NULL, APR_HOOK_FIRST); #endif ap_hook_fixups(sec_check_access, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_child_init(sec_child_init, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_error_log(sec_error_log, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_insert_filter(sec_insert_filter, NULL, NULL, APR_HOOK_FIRST); global_sec_filter_in = ap_register_input_filter("MODSEC_IN", sec_filter_in, NULL, AP_FTYPE_CONTENT_SET) ; global_sec_filter_out = ap_register_output_filter("MODSEC_OUT", sec_filter_out, NULL, AP_FTYPE_CONTENT_SET); } module AP_MODULE_DECLARE_DATA security_module = { STANDARD20_MODULE_STUFF, sec_create_dir_config, /* create per-dir config structures */ sec_merge_dir_config, /* merge per-dir config structures */ sec_create_srv_config, /* create per-server config structures */ sec_merge_srv_config, /* merge per-server config structures */ sec_cmds, /* table of config file commands */ register_hooks /* register hooks */ };