一、相关结构体
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_YAJL
json_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_LUA
lua_State *L;
#endif
#endif
int 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, //a
merge_directory_configs, //b
NULL, /* create_server_config */
NULL, /* merge_server_configs */
module_directives, //c
register_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;
else
if(var->param_data!=NULL) //正则表达式
msc_regexec((msc_regex_t *)var->param_data,...)
else
if(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) //串行日志记录-我们已经有一个打开的文件描述符
|->else
apr_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_dir
entry_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)//将数据发送到过滤器流