leevis.com
leevis.com copied to clipboard
naxsi 模块代码分析
概述
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;
- main_rule 指令对应的解析配置回调函数为ngx_http_dummy_read_main_conf。 main_rule id:1002 "str:0x" "msg: test" "mz:ARGS|$HEADERS_VAR:Cookie" "s:$SQL:2";
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);
}
- denied_url 指令的回调函数为ngx_http_naxsi_ud_loc_conf。 该指令配置在location块内,指定block跳转的location。
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;
}
}
- check_rule 指令的回调函数是ngx_http_naxsi_cr_loc_conf。 check_rule "$SQL >= 5" BLOCK;
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);
}
- 白名单的添加 basic_rule 指令通过wl支持添加基础指令白名单。格式: basic_rule "wl:1,2,3" "mz:$ARGS_VAR:test"; 意思是说,指令编号为1,2,3的指令对test这个参数不适用。 指令还是basic_rule,那么处理函数是ngx_http_dummy_read_conf。
在处理"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);
}
- 注册回调函数ngx_http_dummy_init
/*
** 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结构体,其中一个为白名单。