“ModSecurity2”源码分析
一、相關結構體
struct msre_engine{apr_pool_t *mp;apr_table_t *variables;apr_table_t *operators;apr_table_t *actions;apr_table_t *tfns;apr_table_t *reqbody_processors; }; //在msre_engine_variable_register()函數中初始化metadata使用的結構體 struct msre_var_metadata {const char *name;unsigned int type; /* VAR_TYPE_ constants*/unsigned int argc_min;unsigned int argc_max;fn_var_validate_t validate;fn_var_generate_t generate;unsigned int is_cacheable;/* 0-no,1-yes*/unsigned int availability;/*when does this variable become available?*/ }; struct msc_string { char *name;unsigned int name_len;cahr *value;unsigned int value_len; }; //運算符元數據結構體 struct msre_op_metadata {const char *name;fn_op_param_init_t param_init;fn_op_execute_t execute; }; //轉換函數結構體 struct msre_tfn_metadata {const char *name;fn_tfn_execute_t execute; } //引擎動作結構體 struct msre_action_metadata {const char *name;unsigned int type;unsigned int argc_min;unsigned int argc_max;unsigned int allow_param_plusminus;unsigned int cardinality;unsigned int cardinality_group;fn_action_validate_t validate;fn_action_init_t init;fn_action_execute_t execute; }; //在modsecurity_tx_init()函數出現的modsec_rec結構體 struct modsec_rec {apr_pool_t *mp;msc_engine *modsecurity;request_rec *r_early;request_rec *r;directory_config *dcfg1;directory_config *dcfg2;directory_config *usercfg;directory_config *txcfg;unsigned int reqbody_should_exist;unsigned int reqbody_chunked;unsigned int phase;unsigned int phase_request_headers_complete;unsigned int phase_request_body_complete;apr_bucket_brigade *if_brigade;unsigned int if_seen_eos;unsigned int if_status;unsigned int if_started_forwarding;apr_size_t reqbody_length;apr_bucket_brigade *of_brigade;unsigned int of_status;unsigned int of_done_reading;unsigned int of_skipping;unsigned int of_partial;unsigned int of_is_error;unsigned int resbody_status;apr_size_t resbody_length;char *resbody_data;unsigned int resbody_contains_html;apr_size_t stream_input_length;char *stream_input_data;apr_size_t stream_output_length;char *stream_output_data;unsigned int of_stream_changed;unsigned int if_stream_changed;apr_array_header_t *error_messages;apr_array_header_t *alerts;const char *txid;const char *sessionid;const char *userid;const char *server_software;const char *local_addr;unsigned int local_port;const char *local_user;/*client*/const char *remote_addr;unsigned int remote_port;const char *remote_user;/*useragent*/const char *useragent_ip;/*request*/const char *request_line;const char *request_method;const char *request_uri;const char *query_string;const char *request_protocol;const char *hostname;apr_table_t *request_headers;apr_off_t request_content_length;const char *request_content_type;apr_table_t *arguments;apr_table_t *arguments_to_sanitize;apr_table_t *request_headers_to_sanitize;apr_table_t *response_headers_to_sanitize;apr_table_t *request_cookies;apr_table_t *pattern_to_sanitize;unsigned int urlencoded_error;unsigned int inbound_error;unsigned int outbound_error;unsigned int is_relevant;apr_table_t *tx_vars;apr_table_t *geo_vars;/*response*/unsigned int response_status;const char *status_line;const char *response_protocol;apr_table_t *response_headers;unsigned int response_headers_sent;apr_off_t bytes_sent;/* modsecurity request body processing stuff*/unsigned int msc_reqbody_storage; /*on disk or in memory*/unsigned int msc_reqbody_spilltodisk;unsigned int msc_reqbody_read;apr_pool_t *msc_reqbody_mp;apr_array_header_t *msc_reqbody_chunks;unsigned int msc_reqbody_length;int msc_reqbody_chunk_position;unsigned int msc_reqbody_chunk_offset;msc_data_chunk *msc_reqbody_chunk_current;char *msc_reqbody_buffer;const char *msc_reqbody_filename;int msc_reqbody_fd;msc_data_chunk *msc_reqbody_disk_chunk;const char *msc_reqbody_processor;int msc_reqbody_error;cosnt char *msc_reqbody_error_msg;apr_size_t msc_reqbody_no_files_length;char *msc_full_request_buffer;int msc_full_request_length;char *multipart_filename;char *multipart_name;multipart_data *mpd;xml_data *xml; #ifdef WITH_YAJLjson_data *json #endif/* audit logging */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;unsigned int was_intercepted;unsigned int rule_was_intercepted;unsigned int intercept_phase;msre_actionset *intercept_actionset;const char *intercept_message;/*performance measurement*/apr_time_t request_time;apr_time_t time_phase1;apr_time_t time_phase2;apr_time_t time_phase3;apr_time_t time_phase4;apr_time_t time_phase5;apr_time_t time_storage_read;apr_time_t time_storage_write;apr_time_t time_logging;apr_time_t time_gc;apr_table_t *perf_rules;apr_array_header_t *matched_rules;msc_string *matched_var;int highest_severity;/* upload */int upload_extract_files;int upload_remove_files;int upload_files_count;/* other*/apr_table_t *collections_original;apr_table_t *collections;apr_table_t *collections_dirty;/* rule processing temp pool */apr_pool_t *msc_rule_mptmp;/* content injection*/const char *content_prepend;apr_off_t content_prepend_len;const char *content_append;apr_off_t content_append_len;/*data cache*/apr_hash_t *tcache;apr_size_t tcache_items;/*removed rules*/apr_array_header_t *removed_rules;apr_arrya_header_t *removed_rules_tag;apr_array_header_t *removed_rules_msg;/*removed targets*/apr_table_t *removed_targets;unsigned int allow_scope;/*matched vars*/apr_table_t *matched_vars;void *reqbody_processor_ctx;htmlDocPtr crypto_html_tree; #if defined(WITH_LUA)#ifdef CACHE_LUAlua_State *L;#endif #endifint msc_sdbm_delete_error; }; //在parse_arguments()函數中出現的msc_arg結構體 struct msc_arg {const char *name;unsigned int name_len; unsigned int name_origin_offset;unsigned int name_origin_len;const char *value;unsigned int value_len;unsigned int value_origin_offset;unsigned int value_origin_len;const char *origin; }; //出現在msc_regexec()函數中的msc_regex_t結構體 struct msc_regex_t {void *re;void *pe;const char *pattern; }; //出現在msre_ruleset_process_phase()函數中的msre_ruleset結構體 struct msre_ruleset {apr_pool_t *mp;msre_engine *engine;apr_array_header_t *phase_request_headers;apr_array_header_t *phase_request_body;apr_array_header_t *phase_response_headers;apr_array_header_t *phase_response_body;apr_array_header_t *phase_logging; }; //出現在modsecurity_process_phase()函數中的msre_cache_rec結構體 struct msre_cache_rec {int hits;int changed;int num;const char *path;const char *val;apr_size_t val_len; };?1.1 位于apache2/mod_securitty2.c文件中,有個模塊的入口點,這是掛載到apache主程序的入口:
/* Module entry points 模塊的入口點*/ module AP_MODULE_DECLARE_DATA security2_module = {STANDARD20_MODULE_STUFF,create_directory_config, //amerge_directory_configs, //bNULL, /* create_server_config */NULL, /* merge_server_configs */module_directives, //cregister_hooks //d };? ?上述代碼塊中的a是創建一個directory_config結構體變量,然后賦初值,并return到這個結構體地址。
我們來看看這個結構體內容:
struct directory_config {apr_pool_t *mp;msre_ruleset *ruleset;int is_enabled;int reqbody_access;int reqintercept_oe;int reqbody_buffering;long int reqbody_inmemory_limit;long int reqbody_limit;long int reqbody_no_files_limit;int resbody_access;long int of_limit;apr_table_t *of_mime_types;int of_mime_types_cleared;int of_limit_action;int if_limit_action;const char *debuglog_name;int debuglog_level;apr_file_t *debuglog_fd;int cookie_format;int argument_separator;const char *cookiev0_separator;int rule_inheritance;apr_array_header_t *rule_exceptions;/* -- Audit log -- *//* Max rule time */int max_rule_time;/* Whether audit log should be enabled in the context or not */int auditlog_flag;/* AUDITLOG_SERIAL (single file) or AUDITLOG_CONCURRENT (multiple files) */int auditlog_type; #ifdef WITH_YAJL/* AUDITLOGFORMAT_NATIVE or AUDITLOGFORMAT_JSON */int auditlog_format; #endif/* Mode for audit log directories and files */apr_fileperms_t auditlog_dirperms;apr_fileperms_t auditlog_fileperms;char *auditlog_name;char *auditlog2_name;/* The file descriptors for the files above */apr_file_t *auditlog_fd;apr_file_t *auditlog2_fd;/* For the new-style audit log only, the path where audit log entries will be stored */char *auditlog_storage_dir;char *auditlog_parts;/* A regular expression that determines if a response status is treated as relevant */msc_regex_t *auditlog_relevant_regex;/* Upload */const char *tmp_dir;const char *upload_dir;int upload_keep_files;int upload_validates_files;int upload_filemode; /* int only so NOT_SET works */int upload_file_limit;/* Used only in the configuration phase */msre_rule *tmp_chain_starter;msre_actionset *tmp_default_actionset;apr_table_t *tmp_rule_placeholders;/* Misc */const char *data_dir;const char *webappid;const char *sensor_id;const char *httpBlkey;/* Content injection*/int content_injection_enabled;/* Stream Inspection */int stream_inbody_inspection;int stream_outbody_inspection;/* Geo Lookup */geo_db *geo;/* Gsb Lookup */gsb_db *gsb;/* Unicode map*/unicode_map *u_map;/*Cache */int cache_trans;int cache_trans_incremental;apr_size_t cache_trans_min;apr_size_t cache_trans_max;apr_size_t cache_trans_maxitems;apr_array_header_t *component_signatures;/* Request character encoding */const char *request_encoding;int disable_backend_compression;/* Collection timeout */int col_timeout;/*hash of ids*/apr_hash_t *rule_id_htab;/* Hash */apr_array_header_t *hash_method;const char *crypto_key;int crypto_key_len;const char *crypto_param_name;int hash_is_enabled;int hash_enforcement;int crypto_key_add;int crypto_hash_href_rx;int crypto_hash_faction_rx;int crypto_hash_location_rx;int crypto_hash_iframesrc_rx;int crypto_hash_framesrc_rx;int crypto_hash_href_pm;int crypto_hash_faction_pm;int crypto_hash_location_pm;int crypto_hash_iframesrc_pm;int crypto_hash_framesrc_pm;/* xml */int xml_external_entity; };? 上述代碼塊中的b作用是:合并兩個目錄配置,參數2和參數3分別是_parent和_child,說明是合并兩個父子目錄。
? 上述代碼塊中的c中結構體叫做module_directives,這里面跟apache中的module的參數寫法一樣,調用的函數分別是AP_INIT_TAKE1、AP_INIT_TAKE12等,主要是指令名和參數的個數區別。
? ?上述代碼塊中的d是注冊Apache的模塊鉤子。在此鉤子中,相繼調用了多個函數,比如初始化函數等。
? ? 下面分析register_hooks()中所做的事情:
? ? ?1.1.1 注冊可選函數
#if (!defined(NO_MODSEC_API))/*導出可選的函數在模塊register_hooks函數內注冊可選函數,將可選函數添加到apache內核維護的全局可選函數哈希表中,Optional Function將可選函數注冊到apache內核的全局可選函數哈希表中*/APR_REGISTER_OPTIONAL_FN(modsec_register_tfn);APR_REGISTER_OPTIONAL_FN(modsec_register_operator);API_REGISTER_OPTIONAL_FN(modsec_register_variable);APR_REGISTER_OPTIONAL_FN(modsec_register_reqbody_processor); #endif? ? ?1.1.2 主要的鉤子函數
? ? ? ? ? ?1.1.2.1
ap_hook_pre_config(hook_pre_config, NULL, NULL, APR_HOOK_FIRST); ? ? ? ? ? ? ? ? 上面函數中目的是預配置的初始化,在hook_pre_config()函數中初始化創建ModSecuritty引擎,其中modsecurity是全局變量。然后有條件的注冊了一個modsec_var_log_handler(),看起來是用于log作用的,這里先不分析這個log函數。 hook_pre_config()|->modsecurity=modsecurity_create(mp,MODSEC_ONLINE)|->msre_engine_create(msce->mp);|->apr_pool_create()|->engine=apr_pcalloc()|->engine->tfns=apr_table_make()|->msre_engine_register_default_variables(msce->msre);//在此函數中使用msre_engine_variable_register()函數向引擎中注冊很多個默認變量|->msre_engine_variable_register(); |->msre_var_metadata *metadata = ap_pcalloc();|->賦值(包括變量名,回調函數等,回調函數放到后面舉例介紹)然后apr_table_setn(engine->variables, name,(void *)metadata);|->msre_engine_register_default_operators(msce->msre);//注冊了很多運算符|->msre_engine_op_register()|->msre_op_metadata *metadata = apr_pcalloc()|->賦值(包括變量名,回調函數等,回調函數放到后面舉例介紹)然后apr_table_setn();|->msre_engine_register_default_tfns(msce->msre);|->msre_engine_tfn_register()|->msre_tfn_metadata *metadata = apr_pcalloc()|->賦值(包括變量名,回調函數等,回調函數放到后面舉例介紹)然后apr_table_setn()|->msre_engine_register_default_actions(msce->msre);|->msre_engine_action_register()|->msre_action_metadata *metadata = apr_pcalloc()|->賦值(包括變量名,回調函數等,回調函數放到后面舉例介紹)然后apr_table_setn()? ? ? ? ? ? ? ?通過舉例來說明msre_engine_register_default_variables()函數的需要完成的任務:
/*ARGS_POST*/ msre_engine_variable_register(engine,"ARGS_POST",VAR_LIST,0, 1,var_generic_list_validate,var_args_post_generate,VAR_CACHE,PHASE_REQUEST_BODY ); 其中,var_generic_list_validate()函數中主要判斷了參數是否是一個正則表達式 var_args_post_generate()函數:|->for(i=0;i<arr->nelts;i++) if(strcmp("BODY",arg->origin)!=0) continue;if(var->param==NULL)match=1;elseif(var->param_data!=NULL) //正則表達式msc_regexec((msc_regex_t *)var->param_data,...)elseif(strcasecmp(arg->name,var->param)==0) match=1//簡單的比較if(match) //如果我們有一個匹配,將這個參數添加到集合中apr_table_addn(vartab,rvar->name,(void *)rvar)? ? ? ? ? ? ? ? ?通過舉例來說明msre_engine_register_default_operators()函數需要做的工作,此處的例子中還有一個
1./* contains*/ msre_engine_op_register(engine,"contains",NULL,/*init function to flag var substitution*/msre_op_contains_execute ); 其中,msre_op_contains_execute()函數,參考一個SecRule例子: SecRule REQUEST_LINE "!@contains .php" t:none,deny,status:403 SecRule ARGS:ip "!@contains %{TX.1}" |->msre_op_contains_execute() |->expand_macros(msr,str,rule,msr->mp)//在給定的變量中擴展宏("%{NAME}"實體 |->for(i=0;i<=i_max;i++) { if(target[i] == match[0]) { if((match_length==1) || (memcmp((match+1),(target+i+1),(match_length-1)) == 0)) return 1;//匹配 |->return 0;//沒有匹配 2./* detectSQLi */msre_engine_op_register(engine,"detectSQLi",NULL,msre_op_detectSQLi_execute); 其中msre_op_detectSQLi_execute()函數會使用libinjection/目錄下的相關文件的函數,具體的分析看源代碼,暫不介紹? ? ? ? ? ? ? ? ?通過舉例來說明msre_engine_register_default_tfns()函數的需要完成的任務:
/*lowercase*/ msre_engine_tfn_register(engine,"lowercase",msre_fn_lowercase_execute ); 其中,msre_fn_lowercase_execute()函數具有小寫化的功能 |->msre_fn_lowercase_execute()|->while(i<input_len) {int x = input[i];input[i]=tolower(x);if(x!=input[i]) changed=1;i++;}? ? ? ? ? ? ? ?通過舉例來說明msre_engine_register_default_actions()函數的需要完成的任務:
/*phase*/ msre_engine_action_register(engine,"phase",ACTION_DISRUPTIVE,1, 1,NO_PLUS_MINUS,ACTION_CARDINALITY_ONE,ACTION_CGROUP_NONE,msre_action_phase_validate,msre_action_phase_init,NULL); 其中,msre_action_phase_validate()函數什么也沒做,msre_action_phase_init()函數根據參數名將actionset->phase設置成相應的值 if(strcasecmp(action->param,"request") == 0)actionset->phase = 2;else if(strcasecmp(action->param,"response") == 0)actionset->phase = 4;else if(strcasecmp(action->param,"logging") == 0)actionset->phase = 5;? ? ? ? ? ? ? ?1.1.2.2?
ap_hook_post_config(hook_post_config, postconfig_beforeme_list,postconfig_afterme_list,APR_HOOK_REALLY_LAST);? ? ? ? ? ? ? ? ?由于沒有找到ap_hook_post_config()函數的定義,所以上面函數中的postconfig_beforeme_list
? ? ? ? 和postconfig_afterme_list參數暫時不清楚,我們將重點放在hook_post_config()函數上:
//此函數是(后配置)模塊初始化 |->hook_post_config()|->apr_pool_userdata_get(&init_flag,...)//通過apr函數獲取在當前池中的key的value|->如果init_flag==NULL,調用apr_pool_userdata_set(),否則調用modsecurity_init(modsecurity,mp);//在hook_pre_config()中已經初始化好了modsecurity對象|->modsecurity_init()預置modsecurity引擎,這個函數必須在配置處理完成后被調用,因為Apache需要知道正在運行的用戶名|->rc=apr_global_mutex_create(&msce->auditlog_lock,...)|->rc=apr_global_mutex_create(&msce->geo_lock,...)|->rc=apr_global_mutex_create(&msce->dbm_lock,...)|->real_server_signature=apr_pstrdup(mp, apache_get_server_version()) //存儲原始服務器簽名|->如果real_server_signature不是NULL,則ap_add_version_component()和change_server_signature()//忽略此函數的過程|->#if (!(defined(WIN32) || defined(NETWARE))) 則執行內部一系列chroot功能|->apr_pool_cleanup_register(mp,(void *)s, module_cleanup,apr_pool_cleanup_null);//在主池被銷毀時,為稍后的時間安排主要的清理工作? ? ? ? ? ? ? ? 1.1.2.3?
ap_hook_child_init(hook_child_init,NULL,NULL,APR_HOOK_MIDDLE);? ? ? ? ? ? ? ? ? 上面函數中hook_child_init()函數為每個新的子進程執行初始化
|->hook_child_init()|->modsecurity_child_init(modsecurity);|->xmlInitParser();//在任何其他XML調用之前,需要將此過程調用一次|->apr_status_t rc = apr_global_mutex_child_init()//apr_global_mutex_child_init在子進程中重新打開互斥鎖|->apr_global_mutex_child_init()|->apr_global_mutex_child_init()? ? ? ? ? ? ? ? 1.1.2.4 連接進程鉤子
ap_hook_process_connection(hook_connection_early, NULL, NULL, APR_HOOK_FIRST)? ? ? ? ? ? ? ? ?上面函數中hook_connection_early()函數目的是為連接鉤子限制繁忙狀態的連接數
|->hook_connection_early()|->ap_get_scoreboard_worker(sbh)|->ws_record=ap_get_scoreboard_worker_from_indexes(i,j)|->tree_contains_ip()? ? ? ? ? ? ? ? ?1.1.2.5 事務進程鉤子
ap_hook_post_read_request(hook_request_early,postread_beforeme_list, postread_afterme_list, APR_HOOK_REALLY_FIRST);? ? ? ? ? ? ? ? ? ?上面函數中的hook_request_early()函數初始請求處理,在Apache接受請求頭之后立即執行,該函數將創建事務上下文。 在下面的函數分析中,有幾個定義需要了解一下:
#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_FAKE_REQUEST_BODY 'I' #define AUDITLOG_PART_UPLOADS 'J' #define AUDITLOG_PART_MATCHEDRULES 'K' #define AUDITLOG_PART_LAST 'K' #define AUDITLOG_PART_ENDMARKER 'Z' #define NEXT_CHAIN 1 #define NEXT_RULE 2 #define SKIP_RULES 3 |->hook_request_early()|->msr=create_tx_context(r);//初始化事務上下文并創建初始配置|->msr=apr_pcalloc(r->pool,..)//創建一個新的msr并賦值|->apr_allocator_create(&allocator);//創建一個新的分配器|->apr_allocator_max_free_set(allocator, 1024);//設置當前的閾值,在該閾值中,分配器應該開始向系統返回塊|->apr_pool_create_ex(&msr->mp,r->pool,NULL,allocator);//創建新pool,這個函數是線程安全的,因為多個線程可以同時安全地創建同一個父池的子池,類似地,一個線程可以在另一個線程訪問父池的同時創建一個子池|->apr_allocator_owner_set(allocator, msr->mp);//設置分配器的所有者|->msr->dcfg1=ap_get_module_config(r->per_dir_config,&security2_module)|->msr->usercfg=create_directory_config()//創建特殊的用戶配置,將被用來覆蓋默認設置|->msr->txcfg=create_direcotry_config() //創建一個事務上下文并用我們剛從Apache得到的目錄配置填充它|->msr->txcfg=merge_directory_configs(msr->mp,msr->txcfg,msr->dcfg1)|->init_directory_config(msr->txcfg);//初始化目錄配置|->msr->txid=get_env_var(r, "UNIQUE_ID")//檢索指定的環境變量,當mod_unique_id模塊注冊的時候這個值存在|->apr_table_get(r->notes, name)|->msr->request_uri=r->uri//這里有很多賦值操作,目的是填充tx字段,從r的字段到msr的相關字段的賦值|->msr->request_headers = apr_table_copy(msr->mp,r->headers_in)//創建一個新表,并將另一個表復制到其中|->msr->hostname=ap_get_server_name(r)//從請求中獲取當前的服務器名稱|->modsecurity_tx_init(msr) //調用引擎以繼續初始化,繼續給msr的相關字段賦值|->apr_pool_cleanup_register(msr->mp,msr,modsecurity_tx_cleanup,apr_pool_cleanup_null);|->apr_table_get(msr->request_headers,"Content-Length");//這里判斷了請求是否有正文,總共兩者情況有正文|->apr_table_get(msr->request_headers,"Content-Type")|->parse_arguments() //解析QUERY_STRING字段值|->urldecode_nonstrict_inplace_ex() //進行urldecode處理|->add_argument(msr,arguments,arg) //向msr的成員arguments成員中增加key-value對|->apr_table_addn(arguments,log_escape_nq_ex(msr->mp,arg->name,arg->name_len),(void *)arg)|->if(msr->txcfg->cookie_format==COOKIES_V0) parse_cookies_v0(msr,te[i].val, msr->request_cookies,";")|->apr_strtok(cookie_header,delim,&saveptr)|->else parse_cookies_v1(msr, te[i].val,msr->request_cookies)|->store_tx_context(msr,r); //存儲事務上下文,可以在隨后的階段、重定向或子請求中找到它|->apr_table_setn(r->nots,NOTE_MSR,(void *)msr);//apr_table_setn()向表中添加鍵/值對。如果另一個元素已經具有相同的鍵,那么覆蓋之|->#ifdef REQUEST_EARLY|->if (modsecurity_process_phase(msr, PHASE_REQUEST_HEADERS) > 0) //一個事務階段,由于在modsec_rec結構中已經可用,所以不需要顯示地提供階段號|->modsecurity_process_phase_request_headers(msr); //處理進程請求頭(REQUEST_HEADERS)階段|->rc=msre_ruleset_process_phase(msr->txcfg->ruleset,msr)|->首先確定我們需要使用哪一組規則(包括PHASE_REQUEST_HEADERS,PHASE_REQUEST_BODY等)|->apr_table_clear(msr->matched_vars)//從表中刪除所有元素|->for(i=0;i<arr->nelts;i++)//這是一個循環,針對每一個ruleset中的相應成員(階段)的元素來做處理,一直到整個函數結束|->if(mode==SKIP_RULES) //SKIP_RULES用于跳過所有規則,直到我們用指定的規則ID命中一個占位符,然后在此之后繼續執行|->if(rule->placeholder != RULE_PH_NONE)//跳過任何標記為占位符的規則|->if(mode==NEXT_CHAIN) //當鏈中的一個規則不匹配時,就會使用NEXT_CHAIN,然后我們需要跳過該鏈中的剩余規則,以獲得可以執行的下一個規則|->if((mode == NEXT_RULE)&&(skip>0))//如果我們在這里意味著是NEXT_RULE,如果設置"跳過"參數,則需要跳過|->if(((rule->actionser->id!=NULL) && !apr_is_empty_array(msr->removed_rules)) ||(apr_is_empty_array(msr->removed_rules_tag)==0 ||(apr_is_empty_array(msr->removed_rules_msg)==0)) //檢查該規則是否在運行時被刪除,此處的邏輯塊不分析|->rc=msre_rule_process(rule,msr);//使用一個新的內存子池來處理每個規則|->apr_pool_create(&msr->msc_rule_mptmp,msr->mp)//創建規則處理臨時池|->#if defined(WITH_LUA) msre_rule_process_lua(rule,msr)//處理lua腳本,這里直接不介紹|->msre_rule_process_normal(rule,msr) //對給定的事務執行規則|->apr_table_get(rule->actionset->actions, "multiMatch") //獲取multiMatch字段的值|->for(i=0;i<rule->targets->nelts;i++) {list_count=targets[i]->metadata->generate(msr,targets[i],rule,vartab,mptmp)//這里調用之前初始化的回調函數|->for(i=0;i<arr->nelts;i++) //循環一直到函數結尾,循環遍歷最終目標列表中的目標,根據需要執行轉換,并調用操作符|->if(msr->txcfg->cache_trans != MODSEC_CACHE_DISABLED) //判斷這是不是var緩存|->for(k=0;k<tarr->nelts;k++) //構建轉換函數的最終列表apr_table_addn(normtab,action->param,(void *)action) //增加t的參數到表中|->if(usecache && !multi_match && (crec != NULL) &&(crec == last_crec)) //如果最后一個緩存的tfn是列表中的最后一個,那么我們可以在這里停止并立即執行該操作|->rc = execute_operator(var,rule,msr,acting_actionset, mptmp)//根據給定值調用規則操作符,例如: SecRule REQUEST_HEADERS:Content-Type "text/xml" ...或者 SecRule REQUEST_HEADERS:User-Agent "@contains SECRET_PASSWORD"|->tarr=apr_table_elts(msr->removed_targets)|->telts=(const apr_table_entry_t*)tarr->elts|->for(i=0;i<tarr->nelts;i++) //循環處理msr的removed_targets成員rc=msre_ruleset_rule_matches_exception(rule,re) //if(rc>0) rc=fetch_target_exception(rule,msr,var,exceptions)|->rc=rule->op_metadata->execute(msr,rule, var, &my_error_msg) //此函數調用了op_metadata的回調執行函數,是最關鍵的函數之一,另一個是轉換metadata的回調執行函數和action_metadata的回調函數|->if(((rc==0)&&(rule->op_negated == 0)) || ((rc==1)&&(rule->op_negated==1)))//返回RULE_NO_MATCH|->else //匹配if(rc==0) //記錄日志*(const msre_rule **)apr_array_push(msr->matched_rules) = rule;if (var!=NULL && msr !=NULL)//保存最后匹配的var數據給msr->matched_var的各個成員賦值,給創建的變量mvar賦值apr_table_addn(msr->matched_vars, mvar->name, (void *)mvar)if((acting_actionser->serverity>0)&&(acting_actionset->serverity<msr->highest_severity)&&!rule->actionset->is_chained)msre_perform_nondisruptive_actions(msr,rule,rule->actionset,mptmp)//執行非破壞性操作|->for(i=0;i<tarr->nelts;i++)action->metadata->execute(msr,mptmp,rule,action)//執行action_metadata的回調執行函數if(rule->actionset->is_chained==0)msre_perform_disruptive_actions(msr,rule,acting_actionset,mptmp,my_error_msg)//執行破壞性操作|->for(i=0;i<tarr->nelts;i++)action->metadata->execute(msr,mptmp,rule,action)|->if(actionset->intercept_action_rec->metadata->type==ACTION_DISRUPTIVE)actionset->intercept_action_rec->metadata->execute(msr,mptmp,rule,actionset->intercept_action_rec)|->if((msr->phase==PHASE_LOGGING)||...)apr_array_push(msr->alerts)=msc_alert_message(msr,actionset,NULL,message)//msc_alert_message()格式化一個警告信息|->msc_alert(msr, log_level, actionset, "Warning", message)|->tarr=apr_table_elts(normtab)//從normtab表中獲取元素的,返回整個元素數組的地址|->for(;k<tarr->nelts;k++) if(multi_match && (k==0||tfnchanged)) //在多匹配模式下,我們在開始時執行一次運算符,然后每次變量被轉換函數改變一次rc=execute_operator(var,rule,msr,acting_actionset,mptmp)metadata=(msre_tfn_metadata *)action->param_data;tfnchanged=metadata->execute(mptmp,(unsigned char *)var->value,var->value_len,&rval,&rval_length)//調用metadata的回調函數if(usecache) //這里不介紹,忽略|->if(!multi_match || tfnchanged) //如果沒有啟用多匹配,則執行操作符,或者如果是,我們需要處理最后一個轉換的結果rc=execute_opeartor(var,rule,msr,acting_actionset,mptmp)|->if(rc==RULE_NO_MATCH) //如果返回值rc==RULE_NO_MATCH|->else if(rc==RULE_MATCH)//如果返回值rc==RULE_MATCH|->else if(rc<0) //如果返回值rc小于0,表示規則匹配失敗|->else //剩余的情況表示規則匹配失敗而且未知的返回碼|->modsecurity_process_phase_request_body(msr)|->rc=msre_ruleset_process_phase(msr->txcfg->ruleset,msr)//到這兒,請求體和請求頭的處理基本相同|->modsecurity_process_phase_response_headers(msr);|->rc=msre_ruleset_process_phase(msr->txcfg->ruleset,msr);//到這兒,響應頭和請求頭的處理基本相同|->modsecurity_process_phase_response_body(msr);|->msre_ruleset_process_phase(msr->txcfg->ruleset,msr)//到這兒,響應體和請求頭的處理基本相同|->modsecurity_process_phase_logging(msr);|->msre_ruleset_process_phase(msr->txcfg->ruleset,msr)|->modsecurity_persist_data(msr)|->collection_store(msr,col)|->collections_remove_stale(msr,te[i].key)|->if(msr->is_relevant==0) //這個請求是否與日志記錄有關?is_response_status_relevant(msr,msr->r->status) //檢查狀態|->if((msr->txcfg->upload_keep_files==KEEP_FILES_ON)||...)//如果我們向保存這些文件(如果有的話)|->sec_audit_logger(msr) //調用審計日志記錄器|->#ifdef WITH_YAJL sec_audit_logger_json(msr) //這里不介紹|->sec_audit_logger_native(msr) //以本機格式生成審計日志條目|->msr->new_auditlog_boundary=create_auditlog_boundary(msr->r)|->if(msr->txcfg->auditlog_type != AUDITLOG_CONCURRENT) //串行日志記錄-我們已經有一個打開的文件描述符|->elseapr_md5_init(&msr->new_auditlog_md5ctx)//MD5初始化,開始MD5操作,編寫新的上下文msr->new_auditlog_filename=construct_auditlog_filename(msr->mp,msr->txid)//構造一個文件名,用于存儲審計日志條目entry_filename=msr->txcfg->auditlog_storage_direntry_basename=file_dirname(msr->mp,entry_filename)apr_dir_make_recursive()//在文件系統上創建一個新目錄,但行為類似于“mkdir -p”。根據需要創建中間目錄。如果路徑已經存在,則不會報告錯誤。apr_file_open()|->apr_global_mutex_lock(msr->modsecurity->auditlog_lock)|->sec_auditlog_write(msr,text,strlen(text))|->if(strchr(msr->txcfg->auditlog_parts,AUDITLOG_PART_REQUEST_HEADERS)!=NULL) //REQUEST_HEADERS的日志|->if(strchr(msr->txcfg->auditlog_parts,AUDITLOG_PART_REQUEST_BODY)!=NULL) //REQUEST_BODY|->if(strchr(msr->txcfg->auditlog_parts,AUDITLOG_PART_RESPONSE_HEADERS) !=NULL) //RESPONSE_HEADERS|->if(strchr(msr->txcfg->auditlog_parts,AUDITLOG_PART_RESPONSE_BODY) !=NULL) //RESPONSE_BODY|->剩下的if分支在這里不顯示|->rc=perform_interception(msr) //使用結構本身指定的方法攔截事務,必須返回一個HTTP狀態碼,它將被用來終止事務|->switch(actionset->intercept_action) //確定如何響應和準備日志消息case ACTION_DENY:case ACTION_PROXY:case ACTION_DROP:case ACTION_REDIRECT:expand_macros(msr, var, NULL, msr->mp)case ACTION_ALLOW:case ACTION_PAUSE:case ACTION_ALLOW_PHASE:case ACTION_ALLOW_REQUEST:default:|->msc_alert_message(msr,actionset,NULL,message)|->msc_alert()? ? ? ? ? ? ? ? 1.1.2.6?
ap_hook_fixups(hook_request_late, fixups_beforeme_list, NULL,APR_HOOK_REALLY_FIRST)? ? ? ? ? ? ? ? ? ? ? 上述函數中的hook_request_late()函數作為處理程序鏈中的第一個鉤子,該函數執行ModSecurity請求處理的第二階段
|->hook_request_late()|->msr=retrieve_tx_context(r) //找到事務上下文并確保我們繼續進行|->if(msr->phase_request_body_complete) //這個階段已經完成了嗎?|->msr->dcfg2=(directory_config *)ap_get_module_config(r->per_dir_config,&security2_module)//獲取第二個配置上下文|->msr->txcfg=create_directory_config(msr->mp,NULL)//創建一個事務上下文|->msr->txcfg=merge_directory_configs(msr->mp,msr->txcfg,msr->dcfg2) |->msr->txcfg=merge_directory_configs(msr->mp,msr->txcfg,msr->usercfg);//使用顯示用戶設置更新|->init_directory_config(msr->txcfg)|->rc=read_request_body(msr,&my_error_msg) //從客戶端讀取請求體|->modsecurity_request_body_start(msr, error_msg) |->bb_in=apr_brigade_create()|->do{rc=ap_get_brigade(r->input_filters,...)for(bucket=APR_BRIGADE_FIRST(bb_in);...) //循環遍歷brigade中的Bucket,以便提取可用數據的大小rc=apr_bucket_read(bucket,&buf,&buflen,APR_BLOCK_READ)if(buflen!=0)modsecurity_request_body_store(msr,buf,buflen,error_msg)//存儲一大塊請求體數據if(APR_BUCKET_IS_EOS(bucket))finished_reading=1;msr->if_seen_eos=1;}while(!finished_reading);|->modsecurity_request_body_end(msr,error_msg) //停止接收請求體? ? ? ? ? ? ? ? ?1.1.2.7 Logging
ap_hook_error_log(hook_error_log,NULL,NULL,APR_HOOK_MIDDLE)? ? ? ? ? ? ? ? ? ? ? ? 此函數中的hook_error_log()函數在每次Apache都有要寫入的錯誤日志的東西時調用
|->hook_error_log|->retrieve_tx_context((request_rec *)info->r) //通過查看朱請求和之前的請求來檢索之前存儲的事務上下文 ap_hook_log_transaction(hook_log_transaction,NULL,transaction_afterme_list,APR_HOOK_MIDDLE)? ? ? ? ? ? ? ? ? ? ? ? 上述函數中的hook_log_transaction()函數在每個事務結束時調用
|->hook_log_transaction()|->msr=retrieve_tx_context(r)|->msr->response_protocol=get_response_protocol(origr)|->sec_guardian_logger(r,origr,msr) //Guardian日志記錄器用于連接到web服務器保護的外部腳本——httpd_guardian。|->modsecurity_process_phase(msr, PHASE_LOGGING) //調用引擎來完成剩余的工作? ? ? ? ? ? ? ? ?1.1.2.8 Filter hooks
ap_hook_insert_filter(hook_insert_filter,NULL,NULL,APR_HOOK_FIRST)? ? ? ? ? ? ? ? ? ? ? ? ? 上述函數中的hook_insert_filter()在請求處理開始之前調用,這是我們需要決定是否要連接到輸出過濾器鏈的時候
|->hook_insert_filter()|->msr=retrieve_tx_context(r)//首先發現事務上下文|->ap_add_input_filter("MODSECURITY_IN", msr, r, r->connection) //增加輸入過濾器|->ap_add_output_filter("MODSECURITY_OUT", msr, r, r->connection) //增加輸出過濾器 ap_hook_insert_error_filter(hook_insert_error_filter,NULL,NULL,APR_HOOK_FIRST)? ? ? ? ? ? ? ? ? ? ? ? ? ? ?上述函數中的hook_insert_error_filter()在Apache開始處理錯誤時調用,這是一個插入到輸出過濾器鏈中的機會。
|->hook_insert_error_filter()|->msr=retrieve_tx_context(r)|->ap_add_output_filter("MODSECURITY_OUT",msr,r,r->connection)//如果輸出過濾器已經完成,不要運行此行? ? ? ? ? ? ? 1.1.2.9 注冊一個輸入過濾器
ap_register_input_filter("MODSECURITY_IN", input_filter, NULL, AP_FTYPE_CONTENT_SET)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?上述函數用于在系統中注冊一個輸入過濾器,在執行此注冊之后,可以使用ap_add_input_filter()將過濾器添加到過濾器鏈中,并簡單地指定名稱。其中input_filter()函數會將先前存儲的請求體轉發到鏈。
|->input_filter()|->rc=modsecurity_request_body_retrieve_start(msr,&my_error_msg) //準備轉發請求體|->rc=modsecurity_request_body_retrieve(msr,&chunk,(unsigned int)nbytes,&my_error_msg)|->if(rc==0) modsecurity_request_body_retrieve_end(msr)? ? ? ? ? ? ? ?1.1.2.10 注冊輸出過濾器
ap_register_output_filter("MODSECURITY_OUT", output_filter, NULL, AP_FTYPE_CONTENT_SET - 3)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 確保輸出過濾器在其他模塊之前運行,這樣我們就可以得到一個不被修改的更好的請求
|->output_filter()|->msr->response_protocol=get_response_protocol(r)|->rc=modify_response_header(msr)|->rc=modsecurity_process_phase(msr,PHASE_RESPONSE_HEADERS)|->if(rc>0) perform_interception(msr) //事務需要被中斷|->rc=output_filter_init(msr,f,bb_in) //初始化輸出過濾器switch(rc)case -2:case -1:case 0:|->for(bucket=APR_BRIGADE_FIRST(bb_in);...) {//循環遍歷brigade中的bucket,以便提取可用數據的大小rc=apr_bucket_read(bucket,&buf,&buflen, APR_BLOCK_READ);if(APR_BUCKET_IF_EOS(bucket))bucket_ci=apr_bucket_heap_create(msr->content_append,...)APR_BUCKET_INSERT_BEFORE(bucket,bucket_ci);//在指定的桶前插入一個桶|->ap_save_brigade(f,&msr->of_brigade,&bb_in,msr->mp)|->flatten_response_body(msr) |->rc=modsecurity_process_phase(msr,PHASE_RESPONSE_BODY);//處理階段RESPONSE_BODY|->if(rc>0) perform_interception(msr)|->perpend_content_to_of_brigade(msr, f)|->rc=send_of_brigade(msr, f)|->if(msr->phase<PHASE_RESPONSE_BODY)flatten_response_body(msr)modsecurity_process_phase(msr,PHASE_RESPONSE_BODY)|->inject_content_to_of_brigade(msr,f)|->prepend_content_to_of_brigade(msr, f)|->rc=send_of_brigade(msr,f)//將數據發送到過濾器流創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎
總結
以上是生活随笔為你收集整理的“ModSecurity2”源码分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 学习算法导论-红黑树之摘录
- 下一篇: 随想之一人一句