Open vislee opened 6 years ago
naxsi 是第三方的一个WAF模块,更对请参考《nginx 的 WAF 模块 naxsi》
模块指令比较多,指令配置比较复杂。因此指令解析的代码读起来比较费劲,结构体定义也多。 该模块支持nginx格式的指令【例如:main_rule】,也支持非nginx(可能是Apache格式)的指令【例如:MainRule】,文档中是非nginx格式的指令。
结合配置文件先来看下部分指令解析。吐槽一下该模块并没有遵守nginx的代码风格,我看的时候稍作优化 AStyle -A14 -p -j -c -Y -z2 -k3 -n *.c
// naxsi 模块请求上下文 typedef struct { ngx_array_t *special_scores; ngx_int_t score; /* blocking flags */ ngx_flag_t log:1; ngx_flag_t block:1; ngx_flag_t allow:1; ngx_flag_t drop:1; /* state */ ngx_flag_t wait_for_body:1; //等待读取完body ngx_flag_t ready:1; ngx_flag_t over:1; /* matched rules */ ngx_array_t *matched; /* runtime flags (modifiers) */ ngx_flag_t learning:1; ngx_flag_t enabled:1; ngx_flag_t post_action:1; ngx_flag_t extensive_log:1; /* did libinjection sql/xss matched ? */ ngx_flag_t libinjection_sql:1; //sql注入 ngx_flag_t libinjection_xss:1; // xss跨站脚本攻击 } ngx_http_request_ctx_t; // 基础指令根据匹配区域组织 typedef struct { // 匹配区域是请求的参数 ngx_array_t *get_rules; /*ngx_http_rule_t*/ // 匹配区域是请求body ngx_array_t *body_rules; // 匹配区域是请求头 ngx_array_t *header_rules; // 匹配区域是url ngx_array_t *generic_rules; ngx_array_t *raw_body_rules; ngx_array_t *locations; /*ngx_http_dummy_loc_conf_t*/ ngx_log_t *log; } ngx_http_dummy_main_conf_t; // 自定义匹配区域 // 基础指令的"mz: $ARGS_VAR:string|$HEADERS_VAR_X:regex" // | 分割每一部分对应如下一个结构体 /* ** struct used to store a specific match zone ** in conf : MATCH_ZONE:[GET_VAR|HEADER|POST_VAR]:VAR_NAME: */ typedef struct { /* match in [name] var of body */ ngx_flag_t body_var:1; // 请求body具体变量 /* match in [name] var of headers */ ngx_flag_t headers_var:1; // 请求头具体变量 /* match in [name] var of args */ ngx_flag_t args_var:1; // 参数具体变量 /* match on URL [name] */ ngx_flag_t specific_url:1; // 指定特殊的uri ngx_str_t target; // 匹配的具体目标 /* to be used for regexed match zones */ ngx_regex_compile_t *target_rx; // 正则匹配 ngx_uint_t hash; } ngx_http_custom_rule_location_t; // 每一条基本规则都对应一个基本规则结构体 /* basic rule */ typedef struct { ngx_str_t *str; // string ngx_regex_compile_t *rx; // or regex 编译好的正则表达式 /* ** basic rule can have 4 (so far) kind of matching mechanisms : ** RX, STR, LIBINJ_XSS, LIBINJ_SQL */ enum DETECT_MECHANISM match_type; // 匹配类型,对应基础规则的str rx d:libinj_sql d:libinj_xss /* is the match zone a regex or a string (hashtable) */ ngx_int_t rx_mz; /* ~~~~~ match zones ~~~~~~ */ ngx_int_t zone; /* match in full body (POST DATA) */ ngx_flag_t body:1; ngx_flag_t raw_body:1; ngx_flag_t body_var:1; /* match in all headers */ ngx_flag_t headers:1; ngx_flag_t headers_var:1; /* match in URI */ ngx_flag_t url:1; /* match in args (bla.php?<ARGS>) */ ngx_flag_t args:1; // 匹配args ngx_flag_t args_var:1; /* match on flags (weird_uri, big_body etc. */ ngx_flag_t flags:1; /* match on file upload extension */ ngx_flag_t file_ext:1; /* set if defined "custom" match zone (GET_VAR/POST_VAR/...) */ ngx_flag_t custom_location:1; // 自定义匹配区域 ngx_int_t custom_location_only; /* does the rule targets variable name instead ? */ ngx_int_t target_name; /* custom location match zones list (GET_VAR/POST_VAR ...) */ ngx_array_t *custom_locations; //ngx_http_custom_rule_location_t /* ~~~~~~~ specific flags ~~~~~~~~~ */ ngx_flag_t negative:1; } ngx_http_basic_rule_t; typedef struct { /* type of the rule */ ngx_int_t type; // 规则类型,基础规则由basic_rule\main_rule\check_rule添加 /* simply put a flag if it's a wlr, wl_id array will be used to store the whitelisted IDs */ ngx_flag_t whitelist:1; ngx_array_t *wlid_array; // 白名单id 类型:ngx_int_t /* "common" data for all rules */ ngx_int_t rule_id; // 基础指令的ID ngx_str_t *log_msg; // a specific log message ngx_int_t score; //also handles DENY and ALLOW // 基础指令分值 /* List of scores increased on rule match. */ ngx_array_t *sscores; // 基础指令变量累加分 ngx_flag_t sc_block:1; // ngx_flag_t sc_allow:1; // // end of specific score tag stuff ngx_flag_t block:1; // BLOCK ngx_flag_t allow:1; ngx_flag_t drop:1; ngx_flag_t log:1; /* pointers on specific rule stuff */ ngx_http_basic_rule_t *br; } ngx_http_rule_t; /* TOP level configuration structure */ typedef struct { /* ** basicrule / mainrules, sorted by target zone */ ngx_array_t *get_rules; ngx_array_t *body_rules; ngx_array_t *raw_body_rules; ngx_array_t *header_rules; ngx_array_t *generic_rules; ngx_array_t *check_rules; // ngx_http_check_rule_t 由check_rule 指令配置。 /* raw array of whitelisted rules */ ngx_array_t *whitelist_rules; //ngx_http_rule_t 白名单规则 /* raw array of transformed whitelists */ ngx_array_t *tmp_wlr; //ngx_http_whitelist_rule_t 白名单列表 /* raw array of regex-mz whitelists */ ngx_array_t *rxmz_wlr; // 白名单自定义匹配区域正则匹配 /* hash table of whitelisted URL rules */ ngx_hash_t *wlr_url_hash; // 白名单hash表 /* hash table of whitelisted ARGS rules */ ngx_hash_t *wlr_args_hash; /* hash table of whitelisted BODY rules */ ngx_hash_t *wlr_body_hash; /* hash table of whitelisted HEADERS rules */ // 配置了自定义hedaers区域的白名单hash列表。values 指向tmp_wlr列表 ngx_hash_t *wlr_headers_hash; /* rules that are globally disabled in one location */ ngx_array_t *disabled_rules; // 忽略的基础规则,白名单没有配置匹配区域 /* counters for both processed requests and blocked requests, used in naxsi_fmt */ size_t request_processed; size_t request_blocked; ngx_int_t error; ngx_array_t *persistant_data; ngx_flag_t extensive:1; ngx_flag_t learning:1; ngx_flag_t enabled:1; ngx_flag_t force_disabled:1; ngx_flag_t pushed:1; ngx_flag_t libinjection_sql_enabled:1; ngx_flag_t libinjection_xss_enabled:1; ngx_str_t *denied_url; // 跳转的location,由denied_url指令设置。 /* precomputed hash for dynamic variable lookup, variable themselves are boolean */ ngx_uint_t flag_enable_h; ngx_uint_t flag_learning_h; ngx_uint_t flag_post_action_h; ngx_uint_t flag_extensive_log_h; /* precomputed hash for libinjection dynamic flags */ ngx_uint_t flag_libinjection_xss_h; ngx_uint_t flag_libinjection_sql_h; } ngx_http_dummy_loc_conf_t; #define SUP 1 #define SUP_OR_EQUAL 2 #define INF 3 #define INF_OR_EQUAL 4 /* ** This one is very related to the previous one, ** it's used to store a score rule comparison. ** ie : $XSS > 7 */ typedef struct { ngx_str_t sc_tag; // 指定的变量 ngx_int_t sc_score; // 指定的分数 ngx_int_t cmp; // 指定 变量和分数的关系:SUP、 SUP_OR_EQUAL、 INF、 INF_OR_EQUAL ngx_flag_t block:1; // 指定变量匹配分数后的动作 ngx_flag_t allow:1; ngx_flag_t drop:1; ngx_flag_t log:1; } ngx_http_check_rule_t;
static char * ngx_http_dummy_read_main_conf(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_str_t *value; ngx_http_rule_t rule, *rule_r; ngx_http_dummy_main_conf_t *alcf = conf; if (!alcf || !cf) { return (NGX_CONF_ERROR); /* alloc a new rule */ } value = cf->args->elts; /* parse the line, fill rule struct */ NX_LOG_DEBUG(_debug_main_conf, NGX_LOG_EMERG, cf, 0, "XX-TOP READ CONF %s", value[0].data); if (ngx_strcmp(value[0].data, TOP_MAIN_BASIC_RULE_T) && ngx_strcmp(value[0].data, TOP_MAIN_BASIC_RULE_N)) { ngx_http_dummy_line_conf_error(cf, value); return (NGX_CONF_ERROR); } memset(&rule, 0, sizeof(ngx_http_rule_t)); // 解析指令 if (ngx_http_dummy_cfg_parse_one_rule(cf/*, alcf*/, value, &rule, cf->args->nelts) != NGX_CONF_OK) { /* LCOV_EXCL_START */ ngx_http_dummy_line_conf_error(cf, value); return (NGX_CONF_ERROR); /* LCOV_EXCL_STOP */ } // 基础指令匹配请求头 if (rule.br->headers || rule.br->headers_var) { NX_LOG_DEBUG(_debug_main_conf, NGX_LOG_EMERG, cf, 0, "pushing rule %d in header rules", rule.rule_id); if (alcf->header_rules == NULL) { alcf->header_rules = ngx_array_create(cf->pool, 2, sizeof(ngx_http_rule_t)); if (alcf->header_rules == NULL) { return NGX_CONF_ERROR; /* LCOV_EXCL_LINE */ } } rule_r = ngx_array_push(alcf->header_rules); if (!rule_r) return (NGX_CONF_ERROR); /* LCOV_EXCL_LINE */ memcpy(rule_r, &rule, sizeof(ngx_http_rule_t)); } /* push in body match rules (POST/PUT) */ if (rule.br->body || rule.br->body_var) { NX_LOG_DEBUG(_debug_main_conf, NGX_LOG_EMERG, cf, 0, "pushing rule %d in body rules", rule.rule_id); if (alcf->body_rules == NULL) { alcf->body_rules = ngx_array_create(cf->pool, 2, sizeof(ngx_http_rule_t)); if (alcf->body_rules == NULL) { return NGX_CONF_ERROR; /* LCOV_EXCL_LINE */ } } rule_r = ngx_array_push(alcf->body_rules); if (!rule_r) return (NGX_CONF_ERROR); /* LCOV_EXCL_LINE */ memcpy(rule_r, &rule, sizeof(ngx_http_rule_t)); } /* push in raw body match rules (POST/PUT) xx*/ if (rule.br->raw_body) { NX_LOG_DEBUG(_debug_main_conf, NGX_LOG_EMERG, cf, 0, "pushing rule %d in raw (main) body rules", rule.rule_id); if (alcf->raw_body_rules == NULL) { alcf->raw_body_rules = ngx_array_create(cf->pool, 2, sizeof(ngx_http_rule_t)); if (alcf->raw_body_rules == NULL) { return NGX_CONF_ERROR; /* LCOV_EXCL_LINE */ } } rule_r = ngx_array_push(alcf->raw_body_rules); if (!rule_r) return (NGX_CONF_ERROR); /* LCOV_EXCL_LINE */ memcpy(rule_r, &rule, sizeof(ngx_http_rule_t)); } /* push in generic rules, as it's matching the URI */ if (rule.br->url) { NX_LOG_DEBUG(_debug_main_conf, NGX_LOG_EMERG, cf, 0, "pushing rule %d in generic rules", rule.rule_id); if (alcf->generic_rules == NULL) { alcf->generic_rules = ngx_array_create(cf->pool, 2, sizeof(ngx_http_rule_t)); if (alcf->generic_rules == NULL) { return NGX_CONF_ERROR; /* LCOV_EXCL_LINE */ } } rule_r = ngx_array_push(alcf->generic_rules); if (!rule_r) return (NGX_CONF_ERROR); /* LCOV_EXCL_LINE */ memcpy(rule_r, &rule, sizeof(ngx_http_rule_t)); } /* push in GET arg rules, but we should push in POST rules too */ if (rule.br->args_var || rule.br->args) { NX_LOG_DEBUG(_debug_main_conf, NGX_LOG_EMERG, cf, 0, "pushing rule %d in GET rules", rule.rule_id); if (alcf->get_rules == NULL) { alcf->get_rules = ngx_array_create(cf->pool, 2, sizeof(ngx_http_rule_t)); if (alcf->get_rules == NULL) { return NGX_CONF_ERROR; /* LCOV_EXCL_LINE */ } } rule_r = ngx_array_push(alcf->get_rules); if (!rule_r) return (NGX_CONF_ERROR); /* LCOV_EXCL_LINE */ memcpy(rule_r, &rule, sizeof(ngx_http_rule_t)); } return (NGX_CONF_OK); } // naxsi.h 定义了基本指令所包含的item。 #define ID_T "id:" #define SCORE_T "s:" #define MSG_T "msg:" #define RX_T "rx:" #define STR_T "str:" #define MATCH_ZONE_T "mz:" #define WHITELIST_T "wl:" #define LIBINJ_XSS_T "d:libinj_xss" #define LIBINJ_SQL_T "d:libinj_sql" #define NEGATIVE_T "negative" /* ** Structures related to the configuration parser */ typedef struct { char *prefix; void *(*pars)(ngx_conf_t *, ngx_str_t *, ngx_http_rule_t *); } ngx_http_dummy_parser_t; static ngx_http_dummy_parser_t rule_parser[] = { {ID_T, dummy_id}, {SCORE_T, dummy_score}, {MSG_T, dummy_msg}, {RX_T, dummy_rx}, {STR_T, dummy_str}, {LIBINJ_XSS_T, dummy_libinj_xss}, {LIBINJ_SQL_T, dummy_libinj_sql}, {MATCH_ZONE_T, dummy_zone}, {NEGATIVE_T, dummy_negative}, {WHITELIST_T, dummy_whitelist}, {NULL, NULL} }; // naxsi_config.c // 解析一行规则配置(指令check_rule basic_rule main_rule) /* Parse one rule line */ /* ** in : nb elem, value array, rule to fill ** does : creates a rule struct from configuration line ** For each element name matching a tag ** (cf. rule_parser), then call the associated func. */ void * ngx_http_dummy_cfg_parse_one_rule(ngx_conf_t *cf, ngx_str_t *value, ngx_http_rule_t *current_rule, ngx_int_t nb_elem) { int i, z; void *ret; int valid; if (!value || !value[0].data) { return NGX_CONF_ERROR; } /* ** parse basic rule */ if (!ngx_strcmp(value[0].data, TOP_CHECK_RULE_T) || !ngx_strcmp(value[0].data, TOP_CHECK_RULE_N) || !ngx_strcmp(value[0].data, TOP_BASIC_RULE_T) || !ngx_strcmp(value[0].data, TOP_BASIC_RULE_N) || !ngx_strcmp(value[0].data, TOP_MAIN_BASIC_RULE_T) || !ngx_strcmp(value[0].data, TOP_MAIN_BASIC_RULE_N)) { // 基础规则(check_rule、basic_rule、main_rule) NX_LOG_DEBUG(_debug_cfg_parse_one_rule, NGX_LOG_EMERG, cf, 0, "naxsi-basic rule %V", &(value[1])); current_rule->type = BR; // basic rule current_rule->br = ngx_pcalloc(cf->pool, sizeof(ngx_http_basic_rule_t)); // 基础规则对应的结构体 if (!current_rule->br) { return (NGX_CONF_ERROR); } } else { NX_LOG_DEBUG(_debug_cfg_parse_one_rule, NGX_LOG_EMERG, cf, 0, "Unknown start keyword in rule %V", &(value[1])); return (NGX_CONF_ERROR); } // check each word of config line against each rule for(i = 1; i < nb_elem && value[i].len > 0; i++) { valid = 0; // 表驱动解析基础规则的各个部分 // for (z = 0; rule_parser[z].pars; z++) { if (!ngx_strncmp(value[i].data, rule_parser[z].prefix, strlen(rule_parser[z].prefix))) { ret = rule_parser[z].pars(cf, &(value[i]), current_rule); if (ret != NGX_CONF_OK) { NX_LOG_DEBUG(_debug_cfg_parse_one_rule, NGX_LOG_EMERG, cf, 0, "XX-FAILED PARSING '%s'", value[i].data); return (ret); } valid = 1; } } if (!valid) { return (NGX_CONF_ERROR); } } /* validate the structure, and fill empty fields.*/ if (!current_rule->log_msg) { current_rule->log_msg = ngx_pcalloc(cf->pool, sizeof(ngx_str_t)); current_rule->log_msg->data = NULL; current_rule->log_msg->len = 0; } return (NGX_CONF_OK); }
static char * ngx_http_naxsi_ud_loc_conf(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_dummy_loc_conf_t *alcf = conf, **bar; ngx_http_dummy_main_conf_t *main_cf; ngx_str_t *value; if (!alcf || !cf) { return (NGX_CONF_ERROR); /* LCOV_EXCL_LINE */ } value = cf->args->elts; main_cf = ngx_http_conf_get_module_main_conf(cf, ngx_http_naxsi_module); if (!alcf->pushed) { bar = ngx_array_push(main_cf->locations); if (!bar) { return (NGX_CONF_ERROR); /* LCOV_EXCL_LINE */ } *bar = alcf; alcf->pushed = 1; } /* store denied URL for location */ if ( (!ngx_strcmp(value[0].data, TOP_DENIED_URL_N) || !ngx_strcmp(value[0].data, TOP_DENIED_URL_T)) && value[1].len) { alcf->denied_url = ngx_pcalloc(cf->pool, sizeof(ngx_str_t)); if (!alcf->denied_url) { return (NGX_CONF_ERROR); /* LCOV_EXCL_LINE */ } alcf->denied_url->data = ngx_pcalloc(cf->pool, value[1].len+1); if (!alcf->denied_url->data) { return (NGX_CONF_ERROR); /* LCOV_EXCL_LINE */ } memcpy(alcf->denied_url->data, value[1].data, value[1].len); alcf->denied_url->len = value[1].len; return (NGX_CONF_OK); } else { return NGX_CONF_ERROR; } }
static char * ngx_http_naxsi_cr_loc_conf(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_dummy_loc_conf_t *alcf = conf, **bar; ngx_http_dummy_main_conf_t *main_cf; ngx_str_t *value; ngx_http_check_rule_t *rule_c; unsigned int i; u_char *var_end; if (!alcf || !cf) { return (NGX_CONF_ERROR); } value = cf->args->elts; main_cf = ngx_http_conf_get_module_main_conf(cf, ngx_http_naxsi_module); if (!alcf->pushed) { bar = ngx_array_push(main_cf->locations); if (!bar) { return (NGX_CONF_ERROR); /* LCOV_EXCL_LINE */ } *bar = alcf; alcf->pushed = 1; } if (ngx_strcmp(value[0].data, TOP_CHECK_RULE_T) && ngx_strcmp(value[0].data, TOP_CHECK_RULE_N)) { return (NGX_CONF_ERROR); } /* #ifdef _debug_readconf */ /* ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, */ /* "pushing rule %d in check rules", rule.rule_id); */ /* #endif */ i = 0; if (!alcf->check_rules) { alcf->check_rules = ngx_array_create(cf->pool, 2, sizeof(ngx_http_check_rule_t)); } if (!alcf->check_rules) { return (NGX_CONF_ERROR); /* LCOV_EXCL_LINE */ } rule_c = ngx_array_push(alcf->check_rules); if (!rule_c) return (NGX_CONF_ERROR); /* LCOV_EXCL_LINE */ memset(rule_c, 0, sizeof(ngx_http_check_rule_t)); /* process the first word : score rule */ if (value[1].data[i] == '$') { var_end = (u_char *) ngx_strchr((value[1].data)+i, ' '); if (!var_end) { /* LCOV_EXCL_START */ ngx_http_dummy_line_conf_error(cf, value); return (NGX_CONF_ERROR); /* LCOV_EXCL_STOP */ } rule_c->sc_tag.len = var_end - value[1].data; rule_c->sc_tag.data = ngx_pcalloc(cf->pool, rule_c->sc_tag.len + 1); if (!rule_c->sc_tag.data) { return (NGX_CONF_ERROR); /* LCOV_EXCL_LINE */ } memcpy(rule_c->sc_tag.data, value[1].data, rule_c->sc_tag.len); i += rule_c->sc_tag.len + 1; } else { /* LCOV_EXCL_START */ ngx_http_dummy_line_conf_error(cf, value); return (NGX_CONF_ERROR); /* LCOV_EXCL_STOP */ } // move to next word while (value[1].data[i] && value[1].data[i] == ' ') { i++; } // get the comparison type if (value[1].data[i] == '>' && value[1].data[i+1] == '=') { rule_c->cmp = SUP_OR_EQUAL; } else if (value[1].data[i] == '>' && value[1].data[i+1] != '=') { rule_c->cmp = SUP; } else if (value[1].data[i] == '<' && value[1].data[i+1] == '=') { rule_c->cmp = INF_OR_EQUAL; } else if (value[1].data[i] == '<' && value[1].data[i+1] != '=') { rule_c->cmp = INF; } else { ngx_http_dummy_line_conf_error(cf, value); return (NGX_CONF_ERROR); } // move to next word while (value[1].data[i] && !(value[1].data[i] >= '0' && value[1].data[i] <= '9') && (value[1].data[i] != '-')) { i++; } NX_LOG_DEBUG(_debug_readconf, NGX_LOG_EMERG, cf, 0, "XX-special score in checkrule:%s from (%d)", value[1].data, atoi((const char *)value[1].data+i)); // get the score rule_c->sc_score = atoi((const char *)(value[1].data+i)); /* process the second word : Action rule */ if (ngx_strstr(value[2].data, "BLOCK")) { rule_c->block = 1; } else if (ngx_strstr(value[2].data,"ALLOW")) { rule_c->allow = 1; } else if (ngx_strstr(value[2].data, "LOG")) { rule_c->log = 1; } else if (ngx_strstr(value[2].data, "DROP")) { rule_c->drop = 1; } else { /* LCOV_EXCL_START */ ngx_http_dummy_line_conf_error(cf, value); return (NGX_CONF_ERROR); /* LCOV_EXCL_STOP */ } return (NGX_CONF_OK); }
在处理"wl:xxx,xxx" 这个item上调用dummy_whitelist。
void * dummy_whitelist(ngx_conf_t *r, ngx_str_t *tmp, ngx_http_rule_t *rule) { ngx_array_t *wl_ar; unsigned int i, ct; ngx_int_t *id; ngx_str_t str; str.data = tmp->data + strlen(WHITELIST_T); str.len = tmp->len - strlen(WHITELIST_T); for (ct = 1, i = 0; i < str.len; i++) { if (str.data[i] == ',') { ct++; } } wl_ar = ngx_array_create(r->pool, ct, sizeof(ngx_int_t)); if (!wl_ar) { return (NGX_CONF_ERROR); } NX_LOG_DEBUG(_debug_whitelist, NGX_LOG_EMERG, r, 0, "XX- allocated %d elems for WL", ct); for (i = 0; i < str.len; i++) { if (i == 0 || str.data[i-1] == ',') { id = (ngx_int_t *) ngx_array_push(wl_ar); if (!id) { return (NGX_CONF_ERROR); } *id = (ngx_int_t) atoi((const char *)str.data+i); } } rule->wlid_array = wl_ar; return (NGX_CONF_OK); } /* ** my hugly configuration parsing function. ** should be rewritten, cause code is hugly and not bof proof at all ** does : top level parsing config function, ** see foo_cfg_parse.c for stuff */ static char * ngx_http_dummy_read_conf(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_dummy_loc_conf_t *alcf = conf, **bar; ngx_http_dummy_main_conf_t *main_cf; ngx_str_t *value; ngx_http_rule_t rule, *rule_r; #ifdef _debug_readconf if (cf) { value = cf->args->elts; NX_LOG_DEBUG(_debug_readconf, NGX_LOG_EMERG, cf, 0, "TOP READ CONF %V %V", &(value[0]), &(value[1])); } #endif if (!alcf || !cf) { return (NGX_CONF_ERROR); /* LCOV_EXCL_LINE */ } value = cf->args->elts; main_cf = ngx_http_conf_get_module_main_conf(cf, ngx_http_naxsi_module); if (!alcf->pushed) { bar = ngx_array_push(main_cf->locations); if (!bar) { return (NGX_CONF_ERROR); /* LCOV_EXCL_LINE */ } *bar = alcf; alcf->pushed = 1; } /* ** if it's a basic rule */ if (!ngx_strcmp(value[0].data, TOP_BASIC_RULE_T) || !ngx_strcmp(value[0].data, TOP_BASIC_RULE_N)) { memset(&rule, 0, sizeof(ngx_http_rule_t)); if (ngx_http_dummy_cfg_parse_one_rule(cf, value, &rule, cf->args->nelts) != NGX_CONF_OK) { /* LCOV_EXCL_START */ ngx_http_dummy_line_conf_error(cf, value); return (NGX_CONF_ERROR); /* LCOV_EXCL_STOP */ } /* push in whitelist rules, as it have a whitelist ID array */ if (rule.wlid_array && rule.wlid_array->nelts > 0) { if (alcf->whitelist_rules == NULL) { alcf->whitelist_rules = ngx_array_create(cf->pool, 2, sizeof(ngx_http_rule_t)); if (alcf->whitelist_rules == NULL) { return NGX_CONF_ERROR; /* LCOV_EXCL_LINE */ } } rule_r = ngx_array_push(alcf->whitelist_rules); if (!rule_r) { return (NGX_CONF_ERROR); /* LCOV_EXCL_LINE */ } memcpy(rule_r, &rule, sizeof(ngx_http_rule_t)); } else { /* else push in appropriate ruleset : it's a normal rule */ if (rule.br->headers || rule.br->headers_var) { if (alcf->header_rules == NULL) { alcf->header_rules = ngx_array_create(cf->pool, 2, sizeof(ngx_http_rule_t)); if (alcf->header_rules == NULL) { return NGX_CONF_ERROR; /* LCOV_EXCL_LINE */ } } rule_r = ngx_array_push(alcf->header_rules); if (!rule_r) return (NGX_CONF_ERROR); /* LCOV_EXCL_LINE */ memcpy(rule_r, &rule, sizeof(ngx_http_rule_t)); } /* push in body match rules (POST/PUT) */ if (rule.br->body || rule.br->body_var) { if (alcf->body_rules == NULL) { alcf->body_rules = ngx_array_create(cf->pool, 2, sizeof(ngx_http_rule_t)); if (alcf->body_rules == NULL) { return NGX_CONF_ERROR; /* LCOV_EXCL_LINE */ } } rule_r = ngx_array_push(alcf->body_rules); if (!rule_r) return (NGX_CONF_ERROR); /* LCOV_EXCL_LINE */ memcpy(rule_r, &rule, sizeof(ngx_http_rule_t)); } /* push in raw body match rules (POST/PUT) */ if (rule.br->raw_body) { NX_LOG_DEBUG(_debug_readconf, NGX_LOG_EMERG, cf, 0, "pushing rule %d in (read conf) raw_body rules", rule.rule_id); if (alcf->raw_body_rules == NULL) { alcf->raw_body_rules = ngx_array_create(cf->pool, 2, sizeof(ngx_http_rule_t)); if (alcf->raw_body_rules == NULL) { return NGX_CONF_ERROR; /* LCOV_EXCL_LINE */ } } rule_r = ngx_array_push(alcf->raw_body_rules); if (!rule_r) return (NGX_CONF_ERROR); /* LCOV_EXCL_LINE */ memcpy(rule_r, &rule, sizeof(ngx_http_rule_t)); } /* push in generic rules, as it's matching the URI */ if (rule.br->url) { NX_LOG_DEBUG(_debug_readconf, NGX_LOG_EMERG, cf, 0, "pushing rule %d in generic rules", rule.rule_id); if (alcf->generic_rules == NULL) { alcf->generic_rules = ngx_array_create(cf->pool, 2, sizeof(ngx_http_rule_t)); if (alcf->generic_rules == NULL) { return NGX_CONF_ERROR; /* LCOV_EXCL_LINE */ } } rule_r = ngx_array_push(alcf->generic_rules); if (!rule_r) return (NGX_CONF_ERROR); /* LCOV_EXCL_LINE */ memcpy(rule_r, &rule, sizeof(ngx_http_rule_t)); } /* push in GET arg rules, but we should push in POST rules too */ if (rule.br->args_var || rule.br->args) { NX_LOG_DEBUG(_debug_readconf, NGX_LOG_EMERG, cf, 0, "pushing rule %d in GET rules", rule.rule_id); if (alcf->get_rules == NULL) { alcf->get_rules = ngx_array_create(cf->pool, 2, sizeof(ngx_http_rule_t)); if (alcf->get_rules == NULL) { return NGX_CONF_ERROR; /* LCOV_EXCL_LINE */ } } rule_r = ngx_array_push(alcf->get_rules); if (!rule_r) return (NGX_CONF_ERROR); /* LCOV_EXCL_LINE */ memcpy(rule_r, &rule, sizeof(ngx_http_rule_t)); } } return (NGX_CONF_OK); } ngx_http_dummy_line_conf_error(cf, value); return (NGX_CONF_ERROR); }
/* ** This function sets up handlers for ACCESS_PHASE, ** and will call the hashtable creation function ** (whitelist aggregation) */ static ngx_int_t ngx_http_dummy_init(ngx_conf_t *cf) { ngx_http_handler_pt *h; ngx_http_core_main_conf_t *cmcf; ngx_http_dummy_main_conf_t *main_cf; ngx_http_dummy_loc_conf_t **loc_cf; unsigned int i; cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); main_cf = ngx_http_conf_get_module_main_conf(cf, ngx_http_naxsi_module); if (cmcf == NULL || main_cf == NULL) { return (NGX_ERROR); /*LCOV_EXCL_LINE*/ } // 注册回调函数 /* Register for access phase */ h = ngx_array_push(&cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers); if (h == NULL) { return (NGX_ERROR); /*LCOV_EXCL_LINE*/ } *h = ngx_http_dummy_access_handler; /* Go with each locations registred in the srv_conf. */ loc_cf = main_cf->locations->elts; for (i = 0; i < main_cf->locations->nelts; i++) { if (loc_cf[i]->enabled && (!loc_cf[i]->denied_url || loc_cf[i]->denied_url->len <= 0)) { /* LCOV_EXCL_START */ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "Missing DeniedURL, abort."); return (NGX_ERROR); /* LCOV_EXCL_STOP */ } loc_cf[i]->flag_enable_h = ngx_hash_key_lc( (u_char *)RT_ENABLE, strlen(RT_ENABLE)); loc_cf[i]->flag_learning_h = ngx_hash_key_lc( (u_char *)RT_LEARNING, strlen(RT_LEARNING)); loc_cf[i]->flag_post_action_h = ngx_hash_key_lc( (u_char *)RT_POST_ACTION, strlen(RT_POST_ACTION)); loc_cf[i]->flag_extensive_log_h = ngx_hash_key_lc( (u_char *)RT_EXTENSIVE_LOG, strlen(RT_EXTENSIVE_LOG)); loc_cf[i]->flag_libinjection_xss_h = ngx_hash_key_lc( (u_char *)RT_LIBINJECTION_XSS, strlen(RT_LIBINJECTION_XSS)); loc_cf[i]->flag_libinjection_sql_h = ngx_hash_key_lc( (u_char *)RT_LIBINJECTION_SQL, strlen(RT_LIBINJECTION_SQL)); // 格式化白名单 if(ngx_http_dummy_create_hashtables_n(loc_cf[i], cf) != NGX_OK) { /* LCOV_EXCL_START */ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "WhiteList Hash building failed"); return (NGX_ERROR); /* LCOV_EXCL_STOP */ } } /* initialize prng (used for fragmented logs) */ srandom(time(0) * getpid()); /* ** initalise internal rules for libinjection sqli/xss ** (needs proper special scores) */ // xss 和sqli 的id 和 打分 nx_int__libinject_sql = ngx_pcalloc(cf->pool, sizeof(ngx_http_rule_t)); nx_int__libinject_xss = ngx_pcalloc(cf->pool, sizeof(ngx_http_rule_t)); if (!nx_int__libinject_xss || !nx_int__libinject_sql) return (NGX_ERROR); nx_int__libinject_sql->sscores = ngx_array_create(cf->pool, 2, sizeof(ngx_http_special_score_t)); nx_int__libinject_xss->sscores = ngx_array_create(cf->pool, 2, sizeof(ngx_http_special_score_t)); if (!nx_int__libinject_sql->sscores || !nx_int__libinject_xss->sscores ) { return (NGX_ERROR); /* LCOV_EXCL_LINE */ } /* internal ID sqli - 17*/ nx_int__libinject_sql->rule_id = 17; /* internal ID xss - 18*/ nx_int__libinject_xss->rule_id = 18; /* libinjection sqli/xss - special score init */ ngx_http_special_score_t *libjct_sql = ngx_array_push(nx_int__libinject_sql->sscores); ngx_http_special_score_t *libjct_xss = ngx_array_push(nx_int__libinject_xss->sscores); if (!libjct_sql || !libjct_xss) return (NGX_ERROR); /* LCOV_EXCL_LINE */ libjct_sql->sc_tag = ngx_pcalloc(cf->pool, sizeof(ngx_str_t)); libjct_xss->sc_tag = ngx_pcalloc(cf->pool, sizeof(ngx_str_t)); if (!libjct_sql->sc_tag || !libjct_xss->sc_tag) { return (NGX_ERROR); /* LCOV_EXCL_LINE */ } libjct_sql->sc_tag->data = ngx_pcalloc(cf->pool, 18 /* LIBINJECTION_SQL */); libjct_xss->sc_tag->data = ngx_pcalloc(cf->pool, 18 /* LIBINJECTION_XSS */); if (!libjct_sql->sc_tag->data || !libjct_xss->sc_tag->data) { return (NGX_ERROR); /* LCOV_EXCL_LINE */ } strncpy((char *)libjct_sql->sc_tag->data, (char *)"$LIBINJECTION_SQL", 17); strncpy((char *)libjct_xss->sc_tag->data, (char *)"$LIBINJECTION_XSS", 17); libjct_xss->sc_tag->len = 17; libjct_sql->sc_tag->len = 17; libjct_sql->sc_score = 8; libjct_xss->sc_score = 8; return (NGX_OK); }
naxsi模块结构体图:
请求执行到NGX_HTTP_REWRITE_PHASE阶段。由ngx_http_core_rewrite_phase函数调用执行。 回调函数为ngx_http_dummy_access_handler。
// 白名单的增加使得该模块复杂度上升一个数量级,因此白名单的组织和查找也是非常的复杂繁琐 // 是否是白名单规则 // name 是key-val的key,有可能也为空。 // target_name 表示匹配的是key-val的key还是val,1位key。 int ngx_http_dummy_is_rule_whitelisted_n(ngx_http_request_t *req, ngx_http_dummy_loc_conf_t *cf, ngx_http_rule_t *r, ngx_str_t *name, enum DUMMY_MATCH_ZONE zone, ngx_int_t target_name) { ngx_int_t k; ngx_http_whitelist_rule_t *b = NULL; unsigned int i; ngx_http_rule_t **dr; ngx_str_t tmp_hashname; ngx_str_t nullname = ngx_null_string; /* if name is NULL, replace it by an empty string */ if (!name) { name = &nullname; } NX_DEBUG(_debug_whitelist_compat, NGX_LOG_DEBUG_HTTP, req->connection->log, 0, "is rule [%d] whitelisted in zone %s for item %V", r->rule_id, zone == ARGS ? "ARGS" : zone == HEADERS ? "HEADERS" : zone == BODY ? "BODY" : zone == URL ? "URL" : zone == FILE_EXT ? "FILE_EXT" : zone == RAW_BODY ? "RAW_BODY" : "UNKNOWN", name); if (target_name) { NX_DEBUG(_debug_whitelist_compat, NGX_LOG_DEBUG_HTTP, req->connection->log, 0, "extra: exception happened in |NAME"); } tmp_hashname.data = NULL; /* Check if the rule is part of disabled rules for this location */ if (cf->disabled_rules) { // 白名单没有配置自定义匹配区域,白名单的规则在该location完全失效。 dr = cf->disabled_rules->elts; for (i = 0; i < cf->disabled_rules->nelts; i++) { /* Is rule disabled ? */ if (nx_check_ids(r->rule_id, dr[i]->wlid_array)) { // 规则是在白名单中 NX_DEBUG(_debug_whitelist_compat, NGX_LOG_DEBUG_HTTP, req->connection->log, 0, "rule %d is disabled somewhere", r->rule_id); /* if it doesn't specify zone, skip zone-check */ if (!dr[i]->br) { NX_DEBUG(_debug_whitelist_compat, NGX_LOG_DEBUG_HTTP, req->connection->log, 0, "no zone, skip zone-check"); continue; } /* If rule target nothing, it's whitelisted everywhere */ if (!(dr[i]->br->args || dr[i]->br->headers || dr[i]->br->body || dr[i]->br->url)) { // 没有配置匹配区域 NX_DEBUG(_debug_whitelist_compat, NGX_LOG_DEBUG_HTTP, req->connection->log, 0, "rule %d is fully disabled", r->rule_id); return (1); } /* if exc is in name, but rule is not specificaly disabled for name (and targets a zone) */ if (target_name != dr[i]->br->target_name) { // 匹配目标不一致,跳过匹配 continue; } switch (zone) { case ARGS: if (dr[i]->br->args) { NX_DEBUG(_debug_whitelist_compat, NGX_LOG_DEBUG_HTTP, req->connection->log, 0, "rule %d is disabled in ARGS", r->rule_id); return (1); } break; case HEADERS: if (dr[i]->br->headers) { NX_DEBUG(_debug_whitelist_compat, NGX_LOG_DEBUG_HTTP, req->connection->log, 0, "rule %d is disabled in HEADERS", r->rule_id); return (1); } break; case BODY: if (dr[i]->br->body) { NX_DEBUG(_debug_whitelist_compat, NGX_LOG_DEBUG_HTTP, req->connection->log, 0, "rule %d is disabled in BODY", r->rule_id); return (1); } break; case RAW_BODY: if (dr[i]->br->body) { NX_DEBUG(_debug_whitelist_compat, NGX_LOG_DEBUG_HTTP, req->connection->log, 0, "rule %d is disabled in BODY", r->rule_id); return (1); } break; case FILE_EXT: if (dr[i]->br->file_ext) { NX_DEBUG(_debug_whitelist_compat, NGX_LOG_DEBUG_HTTP, req->connection->log, 0, "rule %d is disabled in FILE_EXT", r->rule_id); return (1); } break; case URL: if (dr[i]->br->url) { NX_DEBUG(_debug_whitelist_compat, NGX_LOG_DEBUG_HTTP, req->connection->log, 0, "rule %d is disabled in URL zone:%d", r->rule_id, zone); return (1); } break; default: break; } } } } NX_DEBUG(_debug_whitelist_compat, NGX_LOG_DEBUG_HTTP, req->connection->log, 0, "hashing varname [%V]", name); // 以下是查找自定义匹配区域 /* ** check for ARGS_VAR:x(|NAME) whitelists. ** (name) or (#name) */ if (name->len > 0) { NX_DEBUG(_debug_whitelist_compat, NGX_LOG_DEBUG_HTTP, req->connection->log, 0, "hashing varname [%V] (rule:%d) - 'wl:X_VAR:%V'", name, r->rule_id, name); /* try to find in hashtables */ b = nx_find_wl_in_hash(req, name, cf, zone); if (b && ngx_http_dummy_is_whitelist_adapted(b, name, zone, r, req, NAME_ONLY, target_name)) { return (1); } /*prefix hash with '#', to find whitelists that would be done only on ARGS_VAR:X|NAME */ tmp_hashname.len = name->len + 1; /* too bad we have to realloc just to add the '#' */ tmp_hashname.data = ngx_pcalloc(req->pool, tmp_hashname.len + 1); tmp_hashname.data[0] = '#'; memcpy(tmp_hashname.data + 1, name->data, name->len); NX_DEBUG(_debug_whitelist_compat, NGX_LOG_DEBUG_HTTP, req->connection->log, 0, "hashing varname [%V] (rule:%d) - 'wl:X_VAR:%V|NAME'", name, r->rule_id, name); b = nx_find_wl_in_hash(req, &tmp_hashname, cf, zone); ngx_pfree(req->pool, tmp_hashname.data); tmp_hashname.data = NULL; if (b && ngx_http_dummy_is_whitelist_adapted(b, name, zone, r, req, NAME_ONLY, target_name)) { return (1); } } /* Plain URI whitelists */ if (cf->wlr_url_hash && cf->wlr_url_hash->size > 0) { /* check the URL no matter what zone we're in */ tmp_hashname.data = ngx_pcalloc(req->pool, req->uri.len + 1); /* mimic find_wl_in_hash, we are looking in a different hashtable */ if (!tmp_hashname.data) { return (0); } tmp_hashname.len = req->uri.len; k = ngx_hash_strlow(tmp_hashname.data, req->uri.data, req->uri.len); NX_DEBUG(_debug_whitelist_compat, NGX_LOG_DEBUG_HTTP, req->connection->log, 0, "hashing uri [%V] (rule:%d) 'wl:$URI:%V|*'", &(tmp_hashname), r->rule_id, &(tmp_hashname)); b = (ngx_http_whitelist_rule_t *) ngx_hash_find(cf->wlr_url_hash, k, (u_char *) tmp_hashname.data, tmp_hashname.len); ngx_pfree(req->pool, tmp_hashname.data); tmp_hashname.data = NULL; if (b && ngx_http_dummy_is_whitelist_adapted(b, name, zone, r, req, URI_ONLY, target_name)) { return (1); } } /* Lookup for $URL|URL (uri)*/ tmp_hashname.data = ngx_pcalloc(req->pool, req->uri.len + 1); if (!tmp_hashname.data) { return (0); } tmp_hashname.len = req->uri.len; ngx_memcpy(tmp_hashname.data, req->uri.data, req->uri.len); NX_DEBUG(_debug_whitelist_compat, NGX_LOG_DEBUG_HTTP, req->connection->log, 0, "hashing uri#1 [%V] (rule:%d) ($URL:X|URI)", &(tmp_hashname), r->rule_id); b = nx_find_wl_in_hash(req, &(tmp_hashname), cf, zone); ngx_pfree(req->pool, tmp_hashname.data); tmp_hashname.data = NULL; if (b && ngx_http_dummy_is_whitelist_adapted(b, name, zone, r, req, URI_ONLY, target_name)) { return (1); } /* Looking $URL:x|ZONE|NAME */ tmp_hashname.data = ngx_pcalloc(req->pool, req->uri.len + 2); /* should make it sound crit isn't it ?*/ if (!tmp_hashname.data) { return (0); } tmp_hashname.len = req->uri.len + 1; tmp_hashname.data[0] = '#'; ngx_memcpy(tmp_hashname.data + 1, req->uri.data, req->uri.len); NX_DEBUG(_debug_whitelist_compat, NGX_LOG_DEBUG_HTTP, req->connection->log, 0, "hashing uri#3 [%V] (rule:%d) ($URL:X|ZONE|NAME)", &(tmp_hashname), r->rule_id); b = nx_find_wl_in_hash(req, &(tmp_hashname), cf, zone); ngx_pfree(req->pool, tmp_hashname.data); tmp_hashname.data = NULL; if (b && ngx_http_dummy_is_whitelist_adapted(b, name, zone, r, req, URI_ONLY, target_name)) { return (1); } /* Maybe it was $URL+$VAR (uri#name) or (#uri#name) */ tmp_hashname.len = req->uri.len + 1 + name->len; /* one extra byte for target_name '#' */ tmp_hashname.data = ngx_pcalloc(req->pool, tmp_hashname.len + 2); if (target_name) { tmp_hashname.len++; strncat((char *)tmp_hashname.data, "#", 1); } strncat((char *) tmp_hashname.data, (char *)req->uri.data, req->uri.len); strncat((char *)tmp_hashname.data, "#", 1); strncat((char *)tmp_hashname.data, (char *)name->data, name->len); NX_DEBUG(_debug_whitelist_compat, NGX_LOG_DEBUG_HTTP, req->connection->log, 0, "hashing MIX [%V] ($URL:x|$X_VAR:y) or ($URL:x|$X_VAR:y|NAME)", &tmp_hashname); b = nx_find_wl_in_hash(req, &(tmp_hashname), cf, zone); ngx_pfree(req->pool, tmp_hashname.data); if (b && ngx_http_dummy_is_whitelist_adapted(b, name, zone, r, req, MIXED, target_name)) { return (1); } /* ** Look it up in regexed whitelists for matchzones */ if (ngx_http_dummy_is_rule_whitelisted_rx(req, cf, r, name, zone, target_name) == 1) { NX_DEBUG(wl_debug_rx, NGX_LOG_DEBUG_HTTP, req->connection->log, 0, "Whitelisted by RX !"); return (1); } return (0); } /* ** check variable + name against a set of rules, ** checking against 'custom' location rules too. */ // 检测name和value是否有sql注入 xss跨站脚本攻击 void ngx_http_libinjection(ngx_pool_t *pool, ngx_str_t *name, ngx_str_t *value, ngx_http_request_ctx_t *ctx, ngx_http_request_t *req, enum DUMMY_MATCH_ZONE zone) { /* ** Libinjection integration : ** 1 - check if libinjection_sql is explicitly enabled ** 2 - check if libinjection_xss is explicitly enabled ** if 1 is true : perform check on both name and content, ** in case of match, apply internal rule ** increasing the LIBINJECTION_SQL score ** if 2 is true ; same as for '1' but with ** LIBINJECTION_XSS */ sfilter state; int issqli; if (ctx->libinjection_sql) { /* hardcoded call to libinjection on NAME, apply internal rule if matched. */ libinjection_sqli_init(&state, (const char *)name->data, name->len, FLAG_NONE); issqli = libinjection_is_sqli(&state); if (issqli == 1) { ngx_http_apply_rulematch_v_n(nx_int__libinject_sql, ctx, req, name, value, zone, 1, 1); } /* hardcoded call to libinjection on CONTENT, apply internal rule if matched. */ libinjection_sqli_init(&state, (const char *)value->data, value->len, FLAG_NONE); issqli = libinjection_is_sqli(&state); if (issqli == 1) { ngx_http_apply_rulematch_v_n(nx_int__libinject_sql, ctx, req, name, value, zone, 1, 0); } } if (ctx->libinjection_xss) { /* first on var_name */ issqli = libinjection_xss((const char *) name->data, name->len); if (issqli == 1) { ngx_http_apply_rulematch_v_n(nx_int__libinject_xss, ctx, req, name, value, zone, 1, 1); } /* hardcoded call to libinjection on CONTENT, apply internal rule if matched. */ issqli = libinjection_xss((const char *) value->data, value->len); if (issqli == 1) { ngx_http_apply_rulematch_v_n(nx_int__libinject_xss, ctx, req, name, value, zone, 1, 0); } } }
naxsi模块是非常不错的一个waf模块。规则灵活可配,支持打分机制。 规则结构体设计的不太合理,性能有不小的损耗。
typedef struct naxsi_basic_rule_s { ngx_uint_t id; ngx_str_t msg; ngx_str_t str; ngx_str_t rx; ngx_int_t sc; ngx_int_t action; ngx_array_t *scs; } naxsi_basic_rule_t; typedef struct naxsi_rule_s { ngx_str_t key; ngx_str_t val; naxsi_basic_rule_t *b_rule; ngx_flag_t is_white; ngx_flag_t args; ngx_flag_t header; ngx_flag_t body; ngx_flag_t url; } naxsi_rule_t; ngx_array_t header; ngx_array_t args; ngx_array_t url; ngx_array_t body; ngx_hash_t header_spec; ngx_hash_t args_spec; ngx_hash_t url_spec; ngx_array_t body_spec;
MainRule id:4242 "str:z" "s:$RFI:8" "mz:$ARGS_VAR:X|BODY"; BasicRule "wl:4242" "mz:$BODY_VAR:test";
上述指令会生成一个naxsi_basic_rule_t结构体,3个naxsi_rule_t结构体,其中一个为白名单。
概述
naxsi 是第三方的一个WAF模块,更对请参考《nginx 的 WAF 模块 naxsi》
代码分析
模块指令比较多,指令配置比较复杂。因此指令解析的代码读起来比较费劲,结构体定义也多。 该模块支持nginx格式的指令【例如:main_rule】,也支持非nginx(可能是Apache格式)的指令【例如:MainRule】,文档中是非nginx格式的指令。
结合配置文件先来看下部分指令解析。吐槽一下该模块并没有遵守nginx的代码风格,我看的时候稍作优化 AStyle -A14 -p -j -c -Y -z2 -k3 -n *.c
指令解析
在处理"wl:xxx,xxx" 这个item上调用dummy_whitelist。
naxsi模块结构体图:
请求执行
请求执行到NGX_HTTP_REWRITE_PHASE阶段。由ngx_http_core_rewrite_phase函数调用执行。 回调函数为ngx_http_dummy_access_handler。
总结
naxsi模块是非常不错的一个waf模块。规则灵活可配,支持打分机制。 规则结构体设计的不太合理,性能有不小的损耗。
MainRule id:4242 "str:z" "s:$RFI:8" "mz:$ARGS_VAR:X|BODY"; BasicRule "wl:4242" "mz:$BODY_VAR:test";
上述指令会生成一个naxsi_basic_rule_t结构体,3个naxsi_rule_t结构体,其中一个为白名单。