vislee / leevis.com

Blog
87 stars 13 forks source link

naxsi 模块代码分析 #144

Open vislee opened 6 years ago

vislee commented 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模块结构体图:

naxsi_uml

请求执行

请求执行到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结构体,其中一个为白名单。