1 /*
2  * NAXSI, a web application firewall for NGINX
3  * Copyright (C) NBS System – All Rights Reserved
4  * Licensed under GNU GPL v3.0 – See the LICENSE notice for details
5  */
6 
7 #ifndef __NAXSI_H__
8 #define __NAXSI_H__
9 
10 #define NAXSI_VERSION "1.3"
11 
12 #include "ext/libinjection/libinjection_sqli.h"
13 #include "ext/libinjection/libinjection_xss.h"
14 #include <ctype.h>
15 #include <nginx.h>
16 #include <ngx_config.h>
17 #include <ngx_core.h>
18 #include <ngx_event.h>
19 #include <ngx_http.h>
20 #include <ngx_http_core_module.h>
21 #include <ngx_md5.h>
22 #include <pcre.h>
23 
24 extern ngx_module_t ngx_http_naxsi_module;
25 
26 /*
27 ** as the #ifdef #endif for debug are getting really messy ...
28 ** Bellow are all the possibles debug defines. To enable associated feature
29 ** debug, just set it to 1. Do not comment the actual define except if you
30 ** know all the associated debug calls are deleted.
31 ** The idea is that the compiler will optimize out the do { if (0) ... } while
32 *(0);
33 */
34 
35 #define _naxsi_rawbody            0
36 #define _debug_basestr_ruleset    0
37 #define _debug_custom_score       0
38 #define _debug_body_parse         0
39 #define _debug_cfg_parse_one_rule 0
40 #define _debug_zone               0
41 #define _debug_extensive_log      0
42 #define _debug_loc_conf           0
43 #define _debug_main_conf          0
44 #define _debug_mechanics          0
45 #define _debug_json               0
46 #define _debug_modifier           0
47 #define _debug_payload_handler    0
48 #define _debug_post_heavy         0
49 #define _debug_rawbody            0
50 #define _debug_readconf           0
51 #define _debug_rx                 0
52 #define _debug_score              0
53 #define _debug_spliturl_ruleset   0
54 #define _debug_whitelist_compat   0
55 #define _debug_whitelist          0
56 #define _debug_whitelist_heavy    0
57 #define _debug_whitelist_light    0
58 #define _debug_whitelist_ignore   0
59 #define wl_debug_rx               0
60 
61 #ifndef __NAXSI_DEBUG
62 #define __NAXSI_DEBUG
63 #define NX_DEBUG(FEATURE, DEF, LOG, ST, ...)                                                       \
64   do {                                                                                             \
65     if (FEATURE)                                                                                   \
66       ngx_log_debug(DEF, LOG, ST, __VA_ARGS__);                                                    \
67   } while (0)
68 #endif
69 
70 #ifndef __NAXSI_LOG_DEBUG
71 #define __NAXSI_LOG_DEBUG
72 #define NX_LOG_DEBUG(FEATURE, DEF, LOG, ST, ...)                                                   \
73   do {                                                                                             \
74     if (FEATURE)                                                                                   \
75       ngx_conf_log_error(DEF, LOG, ST, __VA_ARGS__);                                               \
76   } while (0)
77 #endif
78 
79 /*
80 ** Here is globally how the structures are organized :
81 **
82 ** [[ngx_http_naxsi_main_conf_t]] is the main structure for the module.
83 ** it contains the core rules and a set of ngx_http_naxsi_loc_conf_t,
84 ** for each NGINX location.
85 ** ---
86 ** [[ngx_http_naxsi_loc_conf_t]] is the main structure for any NGINX
87 ** locations, that is - a web site. It contains both pointers to the core
88 ** rules, as well as whitelists, scores, denied_url and all flags. all
89 ** the data of a nginx location is held into the loc_conf_t struct.
90 ** The sets of rules are actually containted into [[ngx_http_rule_t]] structs.
91 ** ---
92 ** [[ngx_http_rule_t]] structs are used to hold any info about a rule, as well
93 ** as whitelists. (whitelists is just a 'kind' of rule).
94 **
95 */
96 
97 /*
98 ** basic rule can have 4 (so far) kind of matching mechanisms
99 ** RX
100 ** STR
101 ** LIBINJ_XSS
102 ** LIBINJ_SQL
103 */
104 
105 typedef enum DETECT_MECHANISM
106 {
107   NONE = -1,
108   RX,
109   STR,
110   LIBINJ_XSS,
111   LIBINJ_SQL
112 } naxsi_detect_mechanism_t;
113 
114 typedef enum MATCH_TYPE
115 {
116   URI_ONLY = 0,
117   NAME_ONLY,
118   MIXED
119 } naxsi_match_type_t;
120 
121 typedef enum NAXSI_MATCH_ZONE
122 {
123   HEADERS = 0,
124   URL,
125   ARGS,
126   BODY,
127   RAW_BODY,
128   FILE_EXT,
129   UNKNOWN
130 } naxsi_match_zone_t;
131 
132 /*
133 ** struct used to store a specific match zone
134 ** in conf : MATCH_ZONE:[GET_VAR|HEADER|POST_VAR]:VAR_NAME:
135 */
136 typedef struct
137 {
138   /* match in [name] var of body */
139   ngx_flag_t body_var : 1;
140   /* match in [name] var of headers */
141   ngx_flag_t headers_var : 1;
142   /* match in [name] var of args */
143   ngx_flag_t args_var : 1;
144   /* match on URL [name] */
145   ngx_flag_t specific_url : 1;
146   ngx_str_t  target;
147   /* to be used for regexed match zones */
148   ngx_regex_compile_t* target_rx;
149   ngx_uint_t           hash;
150 
151 } ngx_http_custom_rule_location_t;
152 
153 /*
154 ** WhiteList Rules Definition :
155 ** A whitelist contains :
156 ** - an URI
157 **
158 ** - one or several sets containing :
159 **  - an variable name ('foo') associated with a zone ($GET_VAR:foo)
160 **  - one or several rules id to whitelist
161 */
162 
163 typedef struct
164 {
165   /* match in full body (POST DATA) */
166   ngx_flag_t body : 1;
167   /* match in [name] var of body */
168   ngx_flag_t body_var : 1;
169   /* match in all headers */
170   ngx_flag_t headers : 1;
171   /* match in [name] var of headers */
172   ngx_flag_t headers_var : 1;
173   /* match in URI */
174   ngx_flag_t url : 1;
175   /* match in args (bla.php?<ARGS>) */
176   ngx_flag_t args : 1;
177   /* match in [name] var of args */
178   ngx_flag_t args_var : 1;
179   /* match on a global flag : weird_request, big_body etc. */
180   ngx_flag_t flags : 1;
181   /* match on file upload extension */
182   ngx_flag_t file_ext : 1;
183   /* set if defined "custom" match zone (GET_VAR/POST_VAR/...)  */
184   ngx_array_t* ids;
185   ngx_str_t*   name;
186 } ngx_http_whitelist_location_t;
187 
188 /*
189 ** this struct is used to aggregate all whitelist
190 ** that point to the same URI or the same VARNAME
191 ** all the "subrules" will then be stored in the "whitelist_locations"
192 */
193 typedef struct
194 {
195   /*ngx_http_whitelist_location_t **/
196   ngx_array_t* whitelist_locations;
197   /* zone to wich the WL applies */
198   naxsi_match_zone_t zone;
199   /* if the "name" is only an url, specify it */
200   int uri_only : 1;
201   /* does the rule targets the name
202      instead of the content ?*/
203   int target_name;
204 
205   ngx_str_t*   name;
206   ngx_int_t    hash;
207   ngx_array_t* ids;
208 } ngx_http_whitelist_rule_t;
209 
210 /* basic rule */
211 typedef struct
212 {
213   ngx_str_t*           str; // string
214   ngx_regex_compile_t* rx;  // or regex
215                             /*
216                             ** basic rule can have 4 (so far) kind of matching mechanisms :
217                             ** RX, STR, LIBINJ_XSS, LIBINJ_SQL
218                             */
219   naxsi_detect_mechanism_t match_type;
220   /* is the match zone a regex or a string (hashtable) */
221   ngx_int_t rx_mz;
222   /* ~~~~~ match zones ~~~~~~ */
223   ngx_int_t zone;
224   /* match in full body (POST DATA) */
225   ngx_flag_t body_rule : 1;
226   ngx_flag_t body : 1;
227   ngx_flag_t raw_body : 1;
228   ngx_flag_t body_var : 1;
229   /* match in all headers */
230   ngx_flag_t headers : 1;
231   ngx_flag_t headers_var : 1;
232   /* match in URI */
233   ngx_flag_t url : 1;
234   /* match in args (bla.php?<ARGS>) */
235   ngx_flag_t args : 1;
236   ngx_flag_t args_var : 1;
237   /* match on flags (weird_uri, big_body etc. */
238   ngx_flag_t flags : 1;
239   /* match on file upload extension */
240   ngx_flag_t file_ext : 1;
241   /* set if defined "custom" match zone (GET_VAR/POST_VAR/...)  */
242   ngx_flag_t custom_location : 1;
243   ngx_int_t  custom_location_only;
244   /* does the rule targets variable name instead ? */
245   ngx_int_t target_name;
246 
247   /* custom location match zones list (GET_VAR/POST_VAR ...) */
248   ngx_array_t* custom_locations;
249   /* ~~~~~~~ specific flags ~~~~~~~~~ */
250   ngx_flag_t negative : 1;
251 } ngx_http_basic_rule_t;
252 
253 /* define for RULE TYPE in rule_t */
254 #define BR 1
255 
256 /* flags used for 'custom match rules', like $XSS > 7 */
257 #define SUP          1
258 #define SUP_OR_EQUAL 2
259 #define INF          3
260 #define INF_OR_EQUAL 4
261 
262 /*
263 ** This struct is used to store custom scores at runtime.
264 **  ie : $XSS = 7
265 ** tag is the $XSS and sc_score is 7
266 */
267 typedef struct
268 {
269   ngx_str_t* sc_tag;
270   ngx_int_t  sc_score;
271   ngx_flag_t block : 1;
272   ngx_flag_t allow : 1;
273   ngx_flag_t drop : 1;
274   ngx_flag_t log : 1;
275 } ngx_http_special_score_t;
276 
277 /*
278 ** This one is very related to the previous one,
279 ** it's used to store a score rule comparison.
280 ** ie : $XSS > 7
281 */
282 typedef struct
283 {
284   ngx_str_t  sc_tag;
285   ngx_int_t  sc_score;
286   ngx_int_t  cmp;
287   ngx_flag_t block : 1;
288   ngx_flag_t allow : 1;
289   ngx_flag_t drop : 1;
290   ngx_flag_t log : 1;
291 } ngx_http_check_rule_t;
292 
293 /* TOP level rule structure */
294 typedef struct
295 {
296   /* type of the rule */
297   ngx_int_t type;
298   /* simply put a flag if it's a wlr,
299      wl_id array will be used to store the whitelisted IDs */
300   ngx_flag_t   whitelist : 1;
301   ngx_array_t* wlid_array;
302   /* "common" data for all rules */
303   ngx_int_t  rule_id;
304   ngx_str_t* log_msg; // a specific log message
305   ngx_int_t  score;   // also handles DENY and ALLOW
306 
307   /* List of scores increased on rule match. */
308   ngx_array_t* sscores;
309   ngx_flag_t   sc_block : 1; //
310   ngx_flag_t   sc_allow : 1; //
311   // end of specific score tag stuff
312   ngx_flag_t block : 1;
313   ngx_flag_t allow : 1;
314   ngx_flag_t drop : 1;
315   ngx_flag_t log : 1;
316   /* pointers on specific rule stuff */
317   ngx_http_basic_rule_t* br;
318 } ngx_http_rule_t;
319 
320 typedef struct
321 {
322   ngx_array_t* get_rules; /*ngx_http_rule_t*/
323   ngx_array_t* body_rules;
324   ngx_array_t* header_rules;
325   ngx_array_t* generic_rules;
326   ngx_array_t* raw_body_rules;
327 
328   ngx_array_t* locations; /*ngx_http_naxsi_loc_conf_t*/
329   ngx_log_t*   log;
330 
331 } ngx_http_naxsi_main_conf_t;
332 
333 /* TOP level configuration structure */
334 typedef struct
335 {
336   /*
337   ** basicrule / mainrules, sorted by target zone
338   */
339   ngx_array_t* get_rules;
340   ngx_array_t* body_rules;
341   ngx_array_t* raw_body_rules;
342   ngx_array_t* header_rules;
343   ngx_array_t* generic_rules;
344   ngx_array_t* check_rules;
345   /* raw array of whitelisted rules */
346   ngx_array_t* whitelist_rules;
347   /* raw array of transformed whitelists */
348   ngx_array_t* tmp_wlr;
349   /* raw array of regex-mz whitelists */
350   ngx_array_t* rxmz_wlr;
351   /* hash table of whitelisted URL rules */
352   ngx_hash_t* wlr_url_hash;
353   /* hash table of whitelisted ARGS rules */
354   ngx_hash_t* wlr_args_hash;
355   /* hash table of whitelisted BODY rules */
356   ngx_hash_t* wlr_body_hash;
357   /* hash table of whitelisted HEADERS rules */
358   ngx_hash_t* wlr_headers_hash;
359   /* hash table of ips to ignore */
360   ngx_hash_t*            ignore_ips;
361   ngx_hash_keys_arrays_t ignore_ips_ha;
362   /* raw array  of cidrs to ignore */
363   ngx_array_t* ignore_cidrs;
364   /* rules that are globally disabled in one location */
365   ngx_array_t* disabled_rules;
366   /* counters for both processed requests and
367      blocked requests, used in naxsi_fmt */
368   size_t       request_processed;
369   size_t       request_blocked;
370   ngx_int_t    error;
371   ngx_array_t* persistant_data;
372   ngx_flag_t   extensive : 1;
373   ngx_flag_t   learning : 1;
374   ngx_flag_t   enabled : 1;
375   ngx_flag_t   force_disabled : 1;
376   ngx_flag_t   pushed : 1;
377   ngx_flag_t   libinjection_sql_enabled : 1;
378   ngx_flag_t   libinjection_xss_enabled : 1;
379   ngx_str_t*   denied_url;
380   /* precomputed hash for dynamic variable lookup,
381      variable themselves are boolean */
382   ngx_uint_t flag_enable_h;
383   ngx_uint_t flag_learning_h;
384   ngx_uint_t flag_post_action_h;
385   ngx_uint_t flag_extensive_log_h;
386   ngx_uint_t flag_json_log_h;
387   /* precomputed hash for
388      libinjection dynamic flags */
389   ngx_uint_t flag_libinjection_xss_h;
390   ngx_uint_t flag_libinjection_sql_h;
391 
392 } ngx_http_naxsi_loc_conf_t;
393 
394 /*
395 ** used to store sets of matched rules during runtime
396 */
397 typedef struct
398 {
399   /* matched in [name] var of body */
400   ngx_flag_t body_var : 1;
401   /* matched in [name] var of headers */
402   ngx_flag_t headers_var : 1;
403   /* matched in [name] var of args */
404   ngx_flag_t args_var : 1;
405   /* matched on URL */
406   ngx_flag_t url : 1;
407   /* matched in filename [name] of args*/
408   ngx_flag_t file_ext : 1;
409   /* matched within the 'NAME' */
410   ngx_flag_t target_name : 1;
411 
412   ngx_str_t*       name;
413   ngx_http_rule_t* rule;
414 } ngx_http_matched_rule_t;
415 
416 /*
417 ** Context structure
418 */
419 typedef struct
420 {
421   ngx_array_t* special_scores;
422   ngx_int_t    score;
423   /* blocking flags */
424   ngx_flag_t log : 1;
425   ngx_flag_t block : 1;
426   ngx_flag_t allow : 1;
427   ngx_flag_t drop : 1;
428   ngx_flag_t ignore : 1;
429   /* state */
430   ngx_flag_t wait_for_body : 1;
431   ngx_flag_t ready : 1;
432   ngx_flag_t over : 1;
433   /* matched rules */
434   ngx_array_t* matched;
435   /* runtime flags (modifiers) */
436   ngx_flag_t learning : 1;
437   ngx_flag_t enabled : 1;
438   ngx_flag_t post_action : 1;
439   ngx_flag_t extensive_log : 1;
440   ngx_flag_t json_log : 1;
441   /* did libinjection sql/xss matched ? */
442   ngx_flag_t libinjection_sql : 1;
443   ngx_flag_t libinjection_xss : 1;
444 } ngx_http_request_ctx_t;
445 
446 /*
447 ** this structure is used only for json parsing.
448 */
449 typedef struct ngx_http_nx_json_s
450 {
451   ngx_str_t                   json;
452   u_char*                     src;
453   ngx_int_t                   off, len;
454   u_char                      c;
455   int                         depth;
456   ngx_http_request_t*         r;
457   ngx_http_request_ctx_t*     ctx;
458   ngx_str_t                   ckey;
459   ngx_http_naxsi_main_conf_t* main_cf;
460   ngx_http_naxsi_loc_conf_t*  loc_cf;
461 } ngx_json_t;
462 
463 #define TOP_DENIED_URL_T       "DeniedUrl"
464 #define TOP_IGNORE_IP_T        "IgnoreIP"
465 #define TOP_IGNORE_CIDR_T      "IgnoreCIDR"
466 #define TOP_LEARNING_FLAG_T    "LearningMode"
467 #define TOP_ENABLED_FLAG_T     "SecRulesEnabled"
468 #define TOP_DISABLED_FLAG_T    "SecRulesDisabled"
469 #define TOP_CHECK_RULE_T       "CheckRule"
470 #define TOP_BASIC_RULE_T       "BasicRule"
471 #define TOP_MAIN_BASIC_RULE_T  "MainRule"
472 #define TOP_LIBINJECTION_SQL_T "LibInjectionSql"
473 #define TOP_LIBINJECTION_XSS_T "LibInjectionXss"
474 
475 /* nginx-style names */
476 #define TOP_DENIED_URL_N       "denied_url"
477 #define TOP_IGNORE_IP_N        "ignore_ip"
478 #define TOP_IGNORE_CIDR_N      "ignore_cidr"
479 #define TOP_LEARNING_FLAG_N    "learning_mode"
480 #define TOP_ENABLED_FLAG_N     "rules_enabled"
481 #define TOP_DISABLED_FLAG_N    "rules_disabled"
482 #define TOP_CHECK_RULE_N       "check_rule"
483 #define TOP_BASIC_RULE_N       "basic_rule"
484 #define TOP_MAIN_BASIC_RULE_N  "main_rule"
485 #define TOP_LIBINJECTION_SQL_N "libinjection_sql"
486 #define TOP_LIBINJECTION_XSS_N "libinjection_xss"
487 
488 /*possible 'tokens' in rule */
489 #define ID_T         "id:"
490 #define SCORE_T      "s:"
491 #define MSG_T        "msg:"
492 #define RX_T         "rx:"
493 #define STR_T        "str:"
494 #define MATCH_ZONE_T "mz:"
495 #define WHITELIST_T  "wl:"
496 #define LIBINJ_XSS_T "d:libinj_xss"
497 #define LIBINJ_SQL_T "d:libinj_sql"
498 #define NEGATIVE_T   "negative"
499 
500 /*
501 ** name of hardcoded variables to
502 ** change behavior of naxsi at runtime
503 */
504 #define RT_EXTENSIVE_LOG    "naxsi_extensive_log"
505 #define RT_JSON_LOG         "naxsi_json_log"
506 #define RT_ENABLE           "naxsi_flag_enable"
507 #define RT_LEARNING         "naxsi_flag_learning"
508 #define RT_POST_ACTION      "naxsi_flag_post_action"
509 #define RT_LIBINJECTION_SQL "naxsi_flag_libinjection_sql"
510 #define RT_LIBINJECTION_XSS "naxsi_flag_libinjection_xss"
511 
512 /*
513 ** To avoid getting DoS'ed, define max depth
514 ** for JSON parser, as it is recursive
515 */
516 #define JSON_MAX_DEPTH 10
517 
518 void*
519 ngx_http_naxsi_cfg_parse_one_rule(ngx_conf_t*      cf,
520                                   ngx_str_t*       value,
521                                   ngx_http_rule_t* rule,
522                                   ngx_int_t        nb_elem);
523 
524 char*
525 strfaststr(const unsigned char* haystack,
526            unsigned int         hl,
527            const unsigned char* needle,
528            unsigned int         nl);
529 
530 #define sstrfaststr(h, hl, n, nl)                                                                  \
531   strfaststr(                                                                                      \
532     (const unsigned char*)(h), (unsigned int)(hl), (const unsigned char*)(n), (unsigned int)(nl));
533 
534 char*
535 strnchr(const char* s, int c, int len);
536 
537 ngx_int_t
538 ngx_http_naxsi_create_hashtables_n(ngx_http_naxsi_loc_conf_t* dlc, ngx_conf_t* cf);
539 
540 void
541 ngx_http_naxsi_data_parse(ngx_http_request_ctx_t* ctx, ngx_http_request_t* r);
542 
543 ngx_int_t
544 ngx_http_output_forbidden_page(ngx_http_request_ctx_t* ctx, ngx_http_request_t* r);
545 
546 int
547 nx_check_ids(ngx_int_t match_id, ngx_array_t* wl_ids);
548 
549 int
550 naxsi_unescape(ngx_str_t* str);
551 
552 u_int
553 naxsi_escape_nullbytes(ngx_str_t* str);
554 
555 void
556 ngx_http_naxsi_json_parse(ngx_http_request_ctx_t* ctx,
557                           ngx_http_request_t*     r,
558                           u_char*                 src,
559                           u_int                   len);
560 
561 void
562 ngx_http_libinjection(ngx_pool_t*             pool,
563                       ngx_str_t*              name,
564                       ngx_str_t*              value,
565                       ngx_http_request_ctx_t* ctx,
566                       ngx_http_request_t*     req,
567                       naxsi_match_zone_t      zone);
568 /*
569 ** JSON parsing prototypes.
570 */
571 ngx_int_t
572 ngx_http_nx_json_forward(ngx_json_t* js);
573 ngx_int_t
574 ngx_http_nx_json_seek(ngx_json_t* js, unsigned char seek);
575 ngx_int_t
576 ngx_http_nx_json_quoted(ngx_json_t* js, ngx_str_t* ve);
577 ngx_int_t
578 ngx_http_nx_json_array(ngx_json_t* js);
579 ngx_int_t
580 ngx_http_nx_json_val(ngx_json_t* js);
581 ngx_int_t
582 ngx_http_nx_json_obj(ngx_json_t* js);
583 
584 /*
585 ** naxsi_runtime
586 **
587 */
588 
589 void
590 ngx_http_naxsi_update_current_ctx_status(ngx_http_request_ctx_t*    ctx,
591                                          ngx_http_naxsi_loc_conf_t* cf,
592                                          ngx_http_request_t*        r,
593                                          ngx_str_t*                 name,
594                                          ngx_str_t*                 value);
595 
596 int
597 ngx_http_process_basic_rule_buffer(ngx_str_t* str, ngx_http_rule_t* rl, ngx_int_t* match);
598 
599 void
600 ngx_http_naxsi_payload_handler(ngx_http_request_t* r);
601 
602 int
603 ngx_http_basestr_ruleset_n(ngx_pool_t*             pool,
604                            ngx_str_t*              name,
605                            ngx_str_t*              value,
606                            ngx_array_t*            rules,
607                            ngx_http_request_t*     req,
608                            ngx_http_request_ctx_t* ctx,
609                            naxsi_match_zone_t      zone);
610 
611 void
612 ngx_http_naxsi_body_parse(ngx_http_request_ctx_t*     ctx,
613                           ngx_http_request_t*         r,
614                           ngx_http_naxsi_loc_conf_t*  cf,
615                           ngx_http_naxsi_main_conf_t* main_cf);
616 
617 void
618 naxsi_log_offending(ngx_str_t*          name,
619                     ngx_str_t*          val,
620                     ngx_http_request_t* req,
621                     ngx_http_rule_t*    rule,
622                     naxsi_match_zone_t  zone,
623                     ngx_int_t           target_name);
624 
625 int
626 ngx_http_apply_rulematch_v_n(ngx_http_rule_t*        r,
627                              ngx_http_request_ctx_t* ctx,
628                              ngx_http_request_t*     req,
629                              ngx_str_t*              name,
630                              ngx_str_t*              value,
631                              naxsi_match_zone_t      zone,
632                              ngx_int_t               nb_match,
633                              ngx_int_t               target_name);
634 
635 /*
636 ** externs for internal rules that requires it.
637 */
638 extern ngx_http_rule_t* nx_int__libinject_sql;
639 extern ngx_http_rule_t* nx_int__libinject_xss;
640 
641 /*libinjection_xss wrapper not exported by libinject_xss.h.*/
642 int
643 libinjection_xss(const char* s, size_t len);
644 
645 #endif /* __NAXSI_H__ */
646