概述

NAXSI means Nginx Anti XSS & SQL Injection。NAXSI 是 Nginx 模块,用于防止跨站攻击、SQL 注入。

安装

1
2
3
4
5
6
7
8
9
# 下载
wget https://github.com/nbs-system/naxsi/archive/0.56.tar.gz
tar zxvf 0.56.tar.gz

# configure
./configure --with-cc=/usr/bin/gcc-4.8 --with-cc-opt=-U_FORTIFY_SOURCE --with-pcre=/opt/pcre-8.38 --with-pcre-jit --with-http_dav_module --with-http_ssl_module --with-http_realip_module --with-openssl=/opt/openssl-1.1.0h --add-module=/opt/nginx-1.13.2/src/dynamod/nginx_accept_language_module --with-ld-opt='-lpcre -Wl,-z,relro -Wl,-rpath,/usr/local/luajit/lib' --add-dynamic-module=/opt/nginx-1.13.2/src/dynamod/naxsi/naxsi_src

make modules
cp objs/ngx_http_naxsi_module.so /srv/nginx/module

基础配置

nginx.conf:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#user  nobody;
worker_processes 1;

#error_log logs/error.log;
#error_log logs/error.log notice;
error_log logs/error.log debug;

#pid logs/nginx.pid;

load_module module/ngx_http_naxsi_module.so;

events {
worker_connections 1024;
}

http {
include mime.types;
default_type application/octet-stream;

include naxsi_core.rules; #导入 naxsi 核心规则

log_format main '$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

sendfile on;
keepalive_timeout 65;

#gzip on;

server {
listen 8080 default;
server_name localhost;

index index.html index.htm index.php;

location /naxsi_test {
SecRulesEnabled;
# LearningMode;
DeniedUrl "/RequestDenied";
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$EVADE >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;

return 200 "ok";
}

location /RequestDenied {
return 403;
}

location ~ .*\.(gif|jpg|jpeg|png|bmp|swf|flv|ico)$ {
expires 30d;
access_log off;
}

location ~ .*\.(js|css)?$ {
expires 7d;
access_log off;
}
}
}

测试命令:

1
curl 'http://127.0.0.1:8080/naxsi_test?a=<>'

可以从日志中看到 naxsi 输出。

命令说明

在 Naxsi 内同时有 Naxsi 风格和 NGINX 风格的指令,左右是相同的。

1. MainRule/main_rule

定义一条规则或白名单。

2. BasicRule/basic_rule

定义一条规则或白名单。

3. DeniedUrl/denied_url

指示 naxsi 将 BLOCK 的请求重定向 DeniedUrl 指定的位置。
示例:

1
2
3
4
5
6
7
8
location / {
...
DeniedUrl "/RequestDenied";
}

location /RequestDenied {
return 418; #I'm a teapot
}

4. CheckRule/check_rule

定义当分值达到阈值采取的动作。可采取动作:

1
LOG, BLOCK, DROP, ALLOW

5. LearningMode/learning_mode

6. SecRulesEnabled/rules_enabled

SecRulesEnabled 用来指示当前 location 是否启用 NAXSI 检查。

7. SecRulesDisabled/rules_disabled

SecRulesDisabled 在当前 location 禁用 NAXSI 检查。

8. LibInjectionSql/libinjection_sql

开启 SQL 注入检测。

9. LibInjectionXss/libinjection_xss

开启跨站攻击检查。

NAXSI 规则指令处理函数

1. SCORE_T 指令处理

SCORE_T 定义:

1
#define SCORE_T "s:"

可选值以及相应处理结果:

可选值 处理结果
BLOCK 本规则 block 位置 1
DROP 本规则 drop 位置 1
ALLOW 本规则 allow 位置 1
LOG 本规则 log 位置 1
[-][0-9] 计算出相应数值

2. ID_T 指令处理

ID_T 定义:

1
#define ID_T "id:"

功能:计算本规则的 ID 号

3. MSG_T 指令处理

MSG_T 定义:

1
#define MSG_T "msg:"

功能:
msg 用来定义当前规则的描述信息。

4. RX_T 指令处理

RX_T 定义:

1
#define RX_T "rx:"

功能:
rx 用于定义正则表达式,内部会预先编译为正则表达式对象,并将其存储在 rule->br->rx 中。

5. STR_T 指令处理

STR_T 定义:

1
#define STR_T "str:"

功能:
str 用于定义字符串匹配,内部使用小写字母表示字符串以忽略大小写,并使 rule->br->str 指向转换后的小写字符串。

6. LIBINJ_XSS_T 指令处理

LIBINJ_XSS_T 定义:

1
#define LIBINJ_XSS_T "d:libinj_xss"

功能:
仅指定本规则为 xss 类型。rule->br->match_type = LIBINJ_XSS

本规则对 GET 请求中 foo 参数进行跨站攻击检查,如果检测到跨站攻击则 DROP 请求。

1
MainRule "id:4241" "s:DROP" "d:libinj_xss" "mz:$ARGS_VAR:foo";

7. LIBINJ_SQL_T 指令处理

LIBINJ_SQL_T 定义:

1
#define LIBINJ_SQL_T "d:libinj_sql"

功能:
仅指定本规则为 SQL 注入类型。rule->br->match_type = LIBINJ_SQL

本规则对 GET 请求中 foo 参数进行 SQL 注入检查,如果检测到 SQL 注入则 DROP 请求。

1
MainRule "id:4241" "s:DROP" "d:libinj_sql" "mz:$ARGS_VAR:foo";

8. MATCH_ZONE_T 指令处理

MATCH_ZONE_T 定义:

1
#define MATCH_ZONE_T "mz:"

功能:
mz 指令用于指定用于匹配的区域。支持:RAW_BODY、BODY、HEADERS、URL、ARGS、NAME、FILE_EXT;用户自定义 ARGS_VAR、HEADERS_VAR、BODY_VAR、URL;以及支持正则表达式的用户自定义区域 ARGS_VAR、HEADERS_VAR、BODY_VAR、URL。
用户自定义、支持正则表达式的用户自定义区域会将值保存在 custom_rule->target 中。

9. NEGATIVE_T 指令处理

NEGATIVE_T 定义:

1
#define NEGATIVE_T "negative"

功能:
rule->br->negative = 1

以下规则将所有请求的 URL 不是以 rest 开头的请求 DROP 掉:

1
MainRule "id:4241" negative "s:DROP" "rx:^/rest/" "mz:URL";

10. WHITELIST_T 指令处理

WHITELIST_T 定义:

1
#define WHITELIST_T "wl:"

功能:
白名单规则列表,使用动态数组保存。

NAXSI 模块指令处理

1. MainRule/main_rule 指令处理函数

MAIN 级别的全局定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
static char *
ngx_http_dummy_read_main_conf(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_dummy_main_conf_t *alcf = conf;
ngx_str_t *value;
ngx_http_rule_t rule, *rule_r;

// 省略代码

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);
}

// 规则解析
if (ngx_http_dummy_cfg_parse_one_rule(cf/*, alcf*/, value, &rule, cf->args->nelts) != NGX_CONF_OK) {
ngx_http_dummy_line_conf_error(cf, value);
return (NGX_CONF_ERROR);
}

// 将解析出来的规则保存到当前 location 相应的配置节点
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) {
// ...
rule_r = ngx_array_push(alcf->body_rules);
memcpy(rule_r, &rule, sizeof(ngx_http_rule_t));
}

/* push in raw body match rules (POST/PUT) xx*/
if (rule.br->raw_body) {
// ...
rule_r = ngx_array_push(alcf->raw_body_rules);
memcpy(rule_r, &rule, sizeof(ngx_http_rule_t));
}

/* push in generic rules, as it's matching the URI */
if (rule.br->url) {
// ...
rule_r = ngx_array_push(alcf->generic_rules);
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) {
// ...
rule_r = ngx_array_push(alcf->get_rules);
memcpy(rule_r, &rule, sizeof(ngx_http_rule_t));
}
return (NGX_CONF_OK);
}

2. BasicRule/basic_rule 指令处理函数

LOCATION 级别的规则定义,当前 LOCATION 使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
/*
** 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;

main_cf = ngx_http_conf_get_module_main_conf(cf, ngx_http_naxsi_module);
if (!alcf->pushed) {
// 将当前 location 的规则放入 MAIN 配置中
bar = ngx_array_push(main_cf->locations);
if (!bar)
return (NGX_CONF_ERROR);
*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))
{
// 规则解析
if (ngx_http_dummy_cfg_parse_one_rule(cf, value, &rule, cf->args->nelts) != NGX_CONF_OK)
{
ngx_http_dummy_line_conf_error(cf, value);
return (NGX_CONF_ERROR);
}

// 根据解析的规则存储到不同的位置
/* 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;
}
}
rule_r = ngx_array_push(alcf->whitelist_rules);
if (!rule_r) {
return (NGX_CONF_ERROR);
}
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;
}
rule_r = ngx_array_push(alcf->header_rules);
if (!rule_r) return (NGX_CONF_ERROR);
memcpy(rule_r, &rule, sizeof(ngx_http_rule_t));
}

/* push in body match rules (POST/PUT) */
if (rule.br->body || rule.br->body_var) {
rule_r = ngx_array_push(alcf->body_rules);
memcpy(rule_r, &rule, sizeof(ngx_http_rule_t));
}

/* push in raw body match rules (POST/PUT) */
if (rule.br->raw_body) {
rule_r = ngx_array_push(alcf->raw_body_rules);
memcpy(rule_r, &rule, sizeof(ngx_http_rule_t));
}

/* push in generic rules, as it's matching the URI */
if (rule.br->url) {
rule_r = ngx_array_push(alcf->generic_rules);
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) {
rule_r = ngx_array_push(alcf->get_rules);
memcpy(rule_r, &rule, sizeof(ngx_http_rule_t));
}
}

return (NGX_CONF_OK);
}

return (NGX_CONF_ERROR);
}

3. 请求处理函数

postconfig 阶段设置 ACCESS 阶段处理函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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*/

// 设置在 NGX_HTTP_REWRITE_PHASE 阶段,保证先于其他 ACCESS 阶段处理函数处理
/* Register for access phase */
h = ngx_array_push(&cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers);
if (h == NULL) return (NGX_ERROR);

*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)) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "Missing DeniedURL, abort.");
return (NGX_ERROR);
}

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) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "WhiteList Hash building failed");
return (NGX_ERROR);
}
}

/* initialize prng (used for fragmented logs) */
srandom(time(0) * getpid());

/*
** initalise internal rules for libinjection sqli/xss
** (needs proper special scores)
*/
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);

/* 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);

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);
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);
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);
}

3. 规则匹配函数

ngx_http_apply_rulematch_v_n 函数的思路是首先判断当前规则、条件是否满足白名单,如果满足则不需要继续进行。如果不满足则根据当前规则进行打分、设置状态(BLOCK、DROP、ALLOw)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
int
ngx_http_apply_rulematch_v_n(ngx_http_rule_t *r, ngx_http_request_ctx_t *ctx, ngx_http_request_t *req, ngx_str_t *name, ngx_str_t *value, enum DUMMY_MATCH_ZONE zone, ngx_int_t nb_match, ngx_int_t target_name)
{
unsigned int found = 0, i, z;
ngx_http_special_score_t *sc, *rsc;
ngx_http_dummy_loc_conf_t *cf;
ngx_http_matched_rule_t *mr;
ngx_str_t empty=ngx_string("");

if (!name)
name = &empty;
if (!value)
value = &empty;

cf = ngx_http_get_module_loc_conf(req, ngx_http_naxsi_module);
if (!cf || !ctx )
return (0);

// 使用白名单过滤,如果在白名单内则不需要继续进行规则匹配
if (ngx_http_dummy_is_rule_whitelisted_n(req, cf, r, name, zone, target_name) == 1) {
NX_DEBUG(_debug_whitelist_light, NGX_LOG_DEBUG_HTTP, req->connection->log, 0,
"rule is whitelisted.");
return (0);
}

NX_DEBUG(_debug_extensive_log, NGX_LOG_DEBUG_HTTP, req->connection->log, 0, "Current extensive log value: %d", ctx->extensive_log);

if (ctx->extensive_log) {
if (target_name)
naxsi_log_offending(value, name, req, r, zone, target_name);
else
naxsi_log_offending(name, value, req, r, zone, target_name);
}

if (nb_match == 0)
nb_match = 1;
if (!ctx->matched)
ctx->matched = ngx_array_create(req->pool, 2, sizeof(ngx_http_matched_rule_t));

/* log stuff, cause this case sux */
if (!ctx->matched)
return (0);
mr = ngx_array_push(ctx->matched);
if (!mr)
return (0);

memset(mr, 0, sizeof(ngx_http_matched_rule_t));
if (target_name)
mr->target_name = 1;

switch(zone) {
case HEADERS:
mr->headers_var = 1;
break;
case URL:
mr->url = 1;
break;
case ARGS:
mr->args_var = 1;
break;
case BODY:
mr->body_var = 1;
break;
case FILE_EXT:
mr->file_ext = 1;
break;
default:
break;
};

mr->rule = r;
// the current "name" ptr will be free by caller, so make a copy
mr->name = ngx_pcalloc(req->pool, sizeof(ngx_str_t));
if (name->len > 0) {
mr->name->data = ngx_pcalloc(req->pool, name->len+1);
memcpy(mr->name->data, name->data, name->len);
mr->name->len = name->len;
} else {
mr->name->data = NULL;
mr->name->len = 0;
}

/* apply special score on rulematch */
if (r->sscores) {
NX_DEBUG(_debug_whitelist, NGX_LOG_DEBUG_HTTP, req->connection->log, 0,
"Rule applies %d custom scores", r->sscores->nelts);

if (!ctx->special_scores) //create the list
ctx->special_scores =
ngx_array_create(req->pool, 1, sizeof(ngx_http_special_score_t));

rsc = r->sscores->elts;
for (z = 0; z < r->sscores->nelts; z++) {
//search into the list for matching special score
found = 0;
sc = ctx->special_scores->elts;
for (i = 0; i < ctx->special_scores->nelts; i++) {
if (rsc[z].sc_tag && sc[i].sc_tag &&
sc[i].sc_tag->len == rsc[z].sc_tag->len &&
!ngx_strcmp(sc[i].sc_tag->data, rsc[z].sc_tag->data))
{
NX_DEBUG(_debug_whitelist, NGX_LOG_DEBUG_HTTP, req->connection->log, 0,
"Special Score (%V) actual=%d,next=%d", rsc[z].sc_tag,
sc[i].sc_score, sc[i].sc_score+(rsc[z].sc_score * nb_match));

sc[i].sc_score += (rsc[z].sc_score * nb_match);
found = 1;
break;
}
}

if (!found) {
NX_DEBUG(_debug_whitelist, NGX_LOG_DEBUG_HTTP, req->connection->log, 0,
"Special Score (%V) next=%d",
rsc[z].sc_tag, (rsc[z].sc_score * nb_match));

sc = ngx_array_push(ctx->special_scores);
if (!sc)
return (0);
memset(sc, 0, sizeof(ngx_http_special_score_t));
sc->sc_tag = rsc[z].sc_tag;
sc->sc_score = (rsc[z].sc_score * nb_match);
}
}
}
/* else, apply normal score */
ctx->score += (r->score * nb_match);
if (r->block)
ctx->block = 1;
if (r->allow)
ctx->allow = 1;
if (r->drop)
ctx->drop = 1;
if (r->log)
ctx->log = 1;
ngx_http_dummy_update_current_ctx_status(ctx, cf, req);
return (1);
}

总结

naxsi 代码逻辑或者对 WAF 抽象不够,使用 naxsi 构建 WAF 并不如使用 OpenResty 构建,OpenResty 更加灵活。