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 #include "naxsi.h"
8 #include "naxsi_net.h"
9 
10 static int
11 naxsi_unescape_uri(u_char** dst, u_char** src, size_t size, ngx_uint_t type);
12 
13 char*
strnchr(const char * s,int c,int len)14 strnchr(const char* s, int c, int len)
15 {
16   int cpt;
17   for (cpt = 0; cpt < len; cpt++) {
18     if (s[cpt] == c) {
19       return ((char*)s + cpt);
20     }
21   }
22   return (NULL);
23 }
24 
25 static char*
strncasechr(const char * s,int c,int len)26 strncasechr(const char* s, int c, int len)
27 {
28   int cpt;
29   for (cpt = 0; cpt < len; cpt++) {
30     if (tolower(s[cpt]) == c) {
31       return ((char*)s + cpt);
32     }
33   }
34   return (NULL);
35 }
36 
37 /*
38 ** strstr: faster, stronger, harder
39 ** (because strstr from libc is very slow)
40 */
41 char*
strfaststr(const unsigned char * haystack,unsigned int hl,const unsigned char * needle,unsigned int nl)42 strfaststr(const unsigned char* haystack,
43            unsigned int         hl,
44            const unsigned char* needle,
45            unsigned int         nl)
46 {
47   char *cpt, *found, *end;
48   if (hl < nl || !haystack || !needle || !nl || !hl)
49     return (NULL);
50   cpt = (char*)haystack;
51   end = (char*)haystack + hl;
52   while (cpt < end) {
53     found = strncasechr((const char*)cpt, (int)needle[0], hl);
54     if (!found) {
55       return (NULL);
56     }
57     if (nl == 1) {
58       return (found);
59     }
60     if (!strncasecmp((const char*)found + 1, (const char*)needle + 1, nl - 1)) {
61       return ((char*)found);
62     } else {
63       if (found + nl >= end) {
64         break;
65       }
66       if (found + nl < end) {
67         /* the haystack is shrinking */
68         cpt = found + 1;
69         hl  = (unsigned int)(end - cpt);
70       }
71     }
72   }
73   return (NULL);
74 }
75 
76 u_int
naxsi_escape_nullbytes(ngx_str_t * str)77 naxsi_escape_nullbytes(ngx_str_t* str)
78 {
79 
80   size_t i         = 0;
81   u_int  nullbytes = 0;
82 
83   for (i = 0; i < str->len; i++) {
84     if (str->data[i] == 0) {
85       str->data[i] = '0';
86       nullbytes++;
87     }
88   }
89   return nullbytes;
90 }
91 
92 /*
93 ** Shamelessly ripped off from https://www.cl.cam.ac.uk/~mgk25/ucs/utf8_check.c
94 ** and adapted to ngx_str_t
95 */
96 unsigned char*
ngx_utf8_check(ngx_str_t * str)97 ngx_utf8_check(ngx_str_t* str)
98 {
99   unsigned int   offset = 0;
100   unsigned char* s;
101 
102   s = str->data;
103 
104   while (offset < str->len && *s) {
105     if (*s < 0x80) {
106       /* 0xxxxxxx */
107       s++;
108       offset++;
109     } else if ((s[0] & 0xe0) == 0xc0) {
110       if (offset + 1 >= str->len) {
111         // not enough bytes
112         return s;
113       }
114       /* 110XXXXx 10xxxxxx */
115       if ((s[1] & 0xc0) != 0x80 || (s[0] & 0xfe) == 0xc0) { /* overlong? */
116         return s;
117       } else {
118         s += 2;
119         offset += 2;
120       }
121     } else if ((s[0] & 0xf0) == 0xe0) {
122       if (offset + 2 >= str->len) {
123         // not enough bytes
124         return s;
125       }
126       /* 1110XXXX 10Xxxxxx 10xxxxxx */
127       if ((s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80 ||
128           (s[0] == 0xe0 && (s[1] & 0xe0) == 0x80) ||               /* overlong? */
129           (s[0] == 0xed && (s[1] & 0xe0) == 0xa0) ||               /* surrogate? */
130           (s[0] == 0xef && s[1] == 0xbf && (s[2] & 0xfe) == 0xbe)) /* U+FFFE or U+FFFF? */
131         return s;
132       else
133         s += 3;
134     } else if ((s[0] & 0xf8) == 0xf0) {
135       if (offset + 3 >= str->len) {
136         // not enough bytes
137         return s;
138       }
139       /* 11110XXX 10XXxxxx 10xxxxxx 10xxxxxx */
140       if ((s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80 || (s[3] & 0xc0) != 0x80 ||
141           (s[0] == 0xf0 && (s[1] & 0xf0) == 0x80) ||      /* overlong? */
142           (s[0] == 0xf4 && s[1] > 0x8f) || s[0] > 0xf4) { /* > U+10FFFF? */
143         return s;
144       } else {
145         s += 4;
146       }
147     } else {
148       return s;
149     }
150   }
151   return NULL;
152 }
153 
154 /*
155    unescape routine returns a sum of :
156  - number of nullbytes present
157  - number of invalid url-encoded characters
158 */
159 int
naxsi_unescape(ngx_str_t * str)160 naxsi_unescape(ngx_str_t* str)
161 {
162   u_char *dst, *src;
163   u_int   nullbytes = 0, bad = 0, i;
164 
165   dst = str->data;
166   src = str->data;
167 
168   bad      = naxsi_unescape_uri(&src, &dst, str->len, 0);
169   str->len = src - str->data;
170   // tmp hack fix, avoid %00 & co (null byte) encoding :p
171   for (i = 0; i < str->len; i++) {
172     if (str->data[i] == 0x0) {
173       nullbytes++;
174       str->data[i] = '0';
175     }
176   }
177   return (nullbytes + bad);
178 }
179 
180 /*
181 ** Patched ngx_unescape_uri :
182 ** The original one does not care if the character following % is in valid
183 *range.
184 ** For example, with the original one :
185 ** '%uff' -> 'uff'
186 */
187 static int
naxsi_unescape_uri(u_char ** dst,u_char ** src,size_t size,ngx_uint_t type)188 naxsi_unescape_uri(u_char** dst, u_char** src, size_t size, ngx_uint_t type)
189 {
190   u_char *d, *s, ch, c, decoded;
191   int     bad = 0;
192 
193   enum
194   {
195     sw_usual = 0,
196     sw_quoted,
197     sw_quoted_second
198   } state;
199 
200   d = *dst;
201   s = *src;
202 
203   state   = sw_usual;
204   decoded = 0;
205 
206   while (size--) {
207 
208     ch = *s++;
209 
210     switch (state) {
211       case sw_usual:
212         if (ch == '%') {
213           state = sw_quoted;
214           break;
215         }
216 
217         *d++ = ch;
218         break;
219 
220       case sw_quoted:
221 
222         if (ch >= '0' && ch <= '9') {
223           decoded = (u_char)(ch - '0');
224           state   = sw_quoted_second;
225           break;
226         }
227 
228         c = (u_char)(ch | 0x20);
229         if (c >= 'a' && c <= 'f') {
230           decoded = (u_char)(c - 'a' + 10);
231           state   = sw_quoted_second;
232           break;
233         }
234 
235         /* the invalid quoted character */
236         bad++;
237         state = sw_usual;
238         *d++  = '%';
239         *d++  = ch;
240         break;
241 
242       case sw_quoted_second:
243 
244         state = sw_usual;
245 
246         if (ch >= '0' && ch <= '9') {
247           ch = (u_char)((decoded << 4) + ch - '0');
248 
249           *d++ = ch;
250 
251           break;
252         }
253 
254         c = (u_char)(ch | 0x20);
255         if (c >= 'a' && c <= 'f') {
256           ch = (u_char)((decoded << 4) + c - 'a' + 10);
257 
258           *d++ = ch;
259 
260           break;
261         }
262         /* the invalid quoted character */
263         /* as it happened in the 2nd part of quoted character,
264            we need to restore the decoded char as well. */
265         *d++ = '%';
266         *d++ = *(s - 2);
267         *d++ = *(s - 1);
268         bad++;
269         break;
270     }
271   }
272 
273   *dst = d;
274   *src = s;
275 
276   return (bad);
277 }
278 
279 /* push rule into disabled rules. */
280 static ngx_int_t
ngx_http_wlr_push_disabled(ngx_conf_t * cf,ngx_http_naxsi_loc_conf_t * dlc,ngx_http_rule_t * curr)281 ngx_http_wlr_push_disabled(ngx_conf_t* cf, ngx_http_naxsi_loc_conf_t* dlc, ngx_http_rule_t* curr)
282 {
283   ngx_http_rule_t** dr;
284 
285   if (!dlc->disabled_rules)
286     dlc->disabled_rules = ngx_array_create(cf->pool, 4, sizeof(ngx_http_rule_t*));
287   if (!dlc->disabled_rules)
288     return (NGX_ERROR);
289   dr = ngx_array_push(dlc->disabled_rules);
290   if (!dr)
291     return (NGX_ERROR);
292   *dr = (ngx_http_rule_t*)curr;
293   return (NGX_OK);
294 }
295 
296 /* merge the two rules into father_wl, meaning
297    ids. Not locations, as we are getting rid of it */
298 static ngx_int_t
ngx_http_wlr_merge(ngx_conf_t * cf,ngx_http_whitelist_rule_t * father_wl,ngx_http_rule_t * curr)299 ngx_http_wlr_merge(ngx_conf_t* cf, ngx_http_whitelist_rule_t* father_wl, ngx_http_rule_t* curr)
300 {
301   uint       i;
302   ngx_int_t* tmp_ptr;
303 
304   NX_LOG_DEBUG(_debug_whitelist, NGX_LOG_EMERG, cf, 0, "[naxsi] merging similar wl(s)");
305 
306   if (!father_wl->ids) {
307     father_wl->ids = ngx_array_create(cf->pool, 3, sizeof(ngx_int_t));
308     if (!father_wl->ids)
309       return (NGX_ERROR);
310   }
311   for (i = 0; i < curr->wlid_array->nelts; i++) {
312     tmp_ptr = ngx_array_push(father_wl->ids);
313     if (!tmp_ptr)
314       return (NGX_ERROR);
315     *tmp_ptr = ((ngx_int_t*)curr->wlid_array->elts)[i];
316     //*tmp_ptr = curr->wlid_array->elts[i];
317   }
318   return (NGX_OK);
319 }
320 
321 /*check rule, returns associed zone, as well as location index.
322   location index refers to $URL:bla or $ARGS_VAR:bla */
323 #define custloc_array(x) ((ngx_http_custom_rule_location_t*)x)
324 
325 static ngx_int_t
ngx_http_wlr_identify(ngx_conf_t * cf,ngx_http_naxsi_loc_conf_t * dlc,ngx_http_rule_t * curr,int * zone,int * uri_idx,int * name_idx)326 ngx_http_wlr_identify(ngx_conf_t*                cf,
327                       ngx_http_naxsi_loc_conf_t* dlc,
328                       ngx_http_rule_t*           curr,
329                       int*                       zone,
330                       int*                       uri_idx,
331                       int*                       name_idx)
332 {
333 
334   uint i;
335 
336   /*
337     identify global match zones (|ARGS|BODY|HEADERS|URL|FILE_EXT)
338    */
339   if (curr->br->body || curr->br->body_var)
340     *zone = BODY;
341   else if (curr->br->headers || curr->br->headers_var)
342     *zone = HEADERS;
343   else if (curr->br->args || curr->br->args_var)
344     *zone = ARGS;
345   else if (curr->br->url) /*don't assume that named $URL means zone is URL.*/
346     *zone = URL;
347   else if (curr->br->file_ext)
348     *zone = FILE_EXT;
349   /*
350     if we're facing a WL in the style $URL:/bla|ARGS (or any other zone),
351     push it to
352    */
353   for (i = 0; i < curr->br->custom_locations->nelts; i++) {
354     /*
355       locate target URL if exists ($URL:/bla|ARGS) or ($URL:/bla|$ARGS_VAR:foo)
356      */
357     if (custloc_array(curr->br->custom_locations->elts)[i].specific_url) {
358       NX_LOG_DEBUG(_debug_whitelist_heavy,
359                    NGX_LOG_EMERG,
360                    cf,
361                    0,
362                    "whitelist has URI %V",
363                    &(custloc_array(curr->br->custom_locations->elts)[i].target));
364 
365       *uri_idx = i;
366     }
367     /*
368       identify named match zones ($ARGS_VAR:bla|$HEADERS_VAR:bla|$BODY_VAR:bla)
369     */
370     if (custloc_array(curr->br->custom_locations->elts)[i].body_var) {
371       NX_LOG_DEBUG(_debug_whitelist_heavy,
372                    NGX_LOG_EMERG,
373                    cf,
374                    0,
375                    "whitelist has body_var %V",
376                    &(custloc_array(curr->br->custom_locations->elts)[i].target));
377 
378       /*#217 : scream on incorrect rules*/
379       if (*name_idx != -1) {
380         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "whitelist can't target more than one BODY item.");
381         return (NGX_ERROR);
382       }
383       *name_idx = i;
384       *zone     = BODY;
385     }
386     if (custloc_array(curr->br->custom_locations->elts)[i].headers_var) {
387       NX_LOG_DEBUG(_debug_whitelist_heavy,
388                    NGX_LOG_EMERG,
389                    cf,
390                    0,
391                    "whitelist has header_var %V",
392                    &(custloc_array(curr->br->custom_locations->elts)[i].target));
393 
394       /*#217 : scream on incorrect rules*/
395       if (*name_idx != -1) {
396         ngx_conf_log_error(
397           NGX_LOG_EMERG, cf, 0, "whitelist can't target more than one HEADER item.");
398         return (NGX_ERROR);
399       }
400       *name_idx = i;
401       *zone     = HEADERS;
402     }
403     if (custloc_array(curr->br->custom_locations->elts)[i].args_var) {
404       NX_LOG_DEBUG(_debug_whitelist_heavy,
405                    NGX_LOG_EMERG,
406                    cf,
407                    0,
408                    "whitelist has arg_var %V",
409                    &(custloc_array(curr->br->custom_locations->elts)[i].target));
410 
411       /*#217 : scream on incorrect rules*/
412       if (*name_idx != -1) {
413         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "whitelist can't target more than one ARGS item.");
414         return (NGX_ERROR);
415       }
416 
417       *name_idx = i;
418       *zone     = ARGS;
419     }
420   }
421   if (*zone == -1)
422     return (NGX_ERROR);
423   return (NGX_OK);
424 }
425 
426 ngx_http_whitelist_rule_t*
ngx_http_wlr_find(ngx_conf_t * cf,ngx_http_naxsi_loc_conf_t * dlc,ngx_http_rule_t * curr,int zone,int uri_idx,int name_idx,char ** fullname)427 ngx_http_wlr_find(ngx_conf_t*                cf,
428                   ngx_http_naxsi_loc_conf_t* dlc,
429                   ngx_http_rule_t*           curr,
430                   int                        zone,
431                   int                        uri_idx,
432                   int                        name_idx,
433                   char**                     fullname)
434 {
435   uint i;
436 
437   /* Create unique string for rule, and try to find it in existing rules.*/
438   /*name AND uri*/
439 
440   if (uri_idx != -1 && name_idx != -1) {
441     NX_LOG_DEBUG(_debug_whitelist_heavy, NGX_LOG_EMERG, cf, 0, "whitelist has uri + name");
442 
443     /* allocate one extra byte in case curr->br->target_name is set. */
444     *fullname =
445       ngx_pcalloc(cf->pool,
446                   custloc_array(curr->br->custom_locations->elts)[name_idx].target.len +
447                     custloc_array(curr->br->custom_locations->elts)[uri_idx].target.len + 3);
448     /* if WL targets variable name instead of content, prefix hash with '#' */
449     if (curr->br->target_name) {
450       NX_LOG_DEBUG(_debug_whitelist_heavy, NGX_LOG_EMERG, cf, 0, "whitelist targets |NAME");
451 
452       strcat(*fullname, (const char*)"#");
453     }
454     strncat(*fullname,
455             (const char*)custloc_array(curr->br->custom_locations->elts)[uri_idx].target.data,
456             custloc_array(curr->br->custom_locations->elts)[uri_idx].target.len);
457     strcat(*fullname, (const char*)"#");
458     strncat(*fullname,
459             (const char*)custloc_array(curr->br->custom_locations->elts)[name_idx].target.data,
460             custloc_array(curr->br->custom_locations->elts)[name_idx].target.len);
461   }
462   /* only uri */
463   else if (uri_idx != -1 && name_idx == -1) {
464     NX_LOG_DEBUG(_debug_whitelist_heavy, NGX_LOG_EMERG, cf, 0, "whitelist has uri");
465 
466     // XXX set flag only_uri
467     *fullname = ngx_pcalloc(
468       cf->pool, custloc_array(curr->br->custom_locations->elts)[uri_idx].target.len + 3);
469     if (curr->br->target_name) {
470       NX_LOG_DEBUG(_debug_whitelist_heavy, NGX_LOG_EMERG, cf, 0, "whitelist targets |NAME");
471 
472       strcat(*fullname, (const char*)"#");
473     }
474 
475     strncat(*fullname,
476             (const char*)custloc_array(curr->br->custom_locations->elts)[uri_idx].target.data,
477             custloc_array(curr->br->custom_locations->elts)[uri_idx].target.len);
478   }
479   /* only name */
480   else if (name_idx != -1) {
481     NX_LOG_DEBUG(_debug_whitelist_heavy, NGX_LOG_EMERG, cf, 0, "whitelist has name");
482 
483     *fullname = ngx_pcalloc(
484       cf->pool, custloc_array(curr->br->custom_locations->elts)[name_idx].target.len + 2);
485     if (curr->br->target_name)
486       strcat(*fullname, (const char*)"#");
487     strncat(*fullname,
488             (const char*)custloc_array(curr->br->custom_locations->elts)[name_idx].target.data,
489             custloc_array(curr->br->custom_locations->elts)[name_idx].target.len);
490   }
491   /* problem houston */
492   else
493     return (NULL);
494 
495   for (i = 0; i < dlc->tmp_wlr->nelts; i++)
496     if (!strcmp((const char*)*fullname,
497                 (const char*)((ngx_http_whitelist_rule_t*)dlc->tmp_wlr->elts)[i].name->data) &&
498         ((ngx_http_whitelist_rule_t*)dlc->tmp_wlr->elts)[i].zone == (uint)zone) {
499       NX_LOG_DEBUG(_debug_whitelist_heavy,
500                    NGX_LOG_EMERG,
501                    cf,
502                    0,
503                    "found existing 'same' WL : %V",
504                    ((ngx_http_whitelist_rule_t*)dlc->tmp_wlr->elts)[i].name);
505 
506       return (&((ngx_http_whitelist_rule_t*)dlc->tmp_wlr->elts)[i]);
507     }
508   return (NULL);
509 }
510 
511 static ngx_int_t
ngx_http_wlr_finalize_hashtables(ngx_conf_t * cf,ngx_http_naxsi_loc_conf_t * dlc)512 ngx_http_wlr_finalize_hashtables(ngx_conf_t* cf, ngx_http_naxsi_loc_conf_t* dlc)
513 {
514   int             get_sz = 0, headers_sz = 0, body_sz = 0, uri_sz = 0;
515   ngx_array_t *   get_ar = NULL, *headers_ar = NULL, *body_ar = NULL, *uri_ar = NULL;
516   ngx_hash_key_t* arr_node;
517   ngx_hash_init_t hash_init;
518   uint            i;
519 
520   NX_LOG_DEBUG(_debug_whitelist_heavy, NGX_LOG_EMERG, cf, 0, "finalizing hashtables");
521 
522   if (dlc->whitelist_rules) {
523 
524     for (i = 0; i < dlc->tmp_wlr->nelts; i++) {
525       switch (((ngx_http_whitelist_rule_t*)dlc->tmp_wlr->elts)[i].zone) {
526         case FILE_EXT:
527         case BODY:
528           body_sz++;
529           break;
530         case HEADERS:
531           headers_sz++;
532           break;
533         case URL:
534           uri_sz++;
535           break;
536         case ARGS:
537           get_sz++;
538           break;
539         case UNKNOWN:
540         default:
541           return (NGX_ERROR);
542       }
543     }
544     NX_LOG_DEBUG(_debug_whitelist_heavy,
545                  NGX_LOG_EMERG,
546                  cf,
547                  0,
548                  "nb items : body:%d headers:%d uri:%d get:%d",
549                  body_sz,
550                  headers_sz,
551                  uri_sz,
552                  get_sz);
553 
554     if (get_sz) {
555       get_ar = ngx_array_create(cf->pool, get_sz, sizeof(ngx_hash_key_t));
556     }
557     if (headers_sz) {
558       headers_ar = ngx_array_create(cf->pool, headers_sz, sizeof(ngx_hash_key_t));
559     }
560     if (body_sz) {
561       body_ar = ngx_array_create(cf->pool, body_sz, sizeof(ngx_hash_key_t));
562     }
563     if (uri_sz) {
564       uri_ar = ngx_array_create(cf->pool, uri_sz, sizeof(ngx_hash_key_t));
565     }
566     for (i = 0; i < dlc->tmp_wlr->nelts; i++) {
567       switch (((ngx_http_whitelist_rule_t*)dlc->tmp_wlr->elts)[i].zone) {
568         case FILE_EXT:
569         case BODY:
570           arr_node = (ngx_hash_key_t*)ngx_array_push(body_ar);
571           break;
572         case HEADERS:
573           arr_node = (ngx_hash_key_t*)ngx_array_push(headers_ar);
574           break;
575         case URL:
576           arr_node = (ngx_hash_key_t*)ngx_array_push(uri_ar);
577           break;
578         case ARGS:
579           arr_node = (ngx_hash_key_t*)ngx_array_push(get_ar);
580           break;
581         default:
582           return (NGX_ERROR);
583       }
584       if (!arr_node) {
585         return (NGX_ERROR);
586       }
587       ngx_memset(arr_node, 0, sizeof(ngx_hash_key_t));
588       arr_node->key = *(((ngx_http_whitelist_rule_t*)dlc->tmp_wlr->elts)[i].name);
589       arr_node->key_hash =
590         ngx_hash_key_lc(((ngx_http_whitelist_rule_t*)dlc->tmp_wlr->elts)[i].name->data,
591                         ((ngx_http_whitelist_rule_t*)dlc->tmp_wlr->elts)[i].name->len);
592       arr_node->value = (void*)&(((ngx_http_whitelist_rule_t*)dlc->tmp_wlr->elts)[i]);
593 #ifdef SPECIAL__debug_whitelist_heavy
594       ngx_conf_log_error(NGX_LOG_EMERG,
595                          cf,
596                          0,
597                          "pushing new WL, zone:%d, target:%V, %d IDs",
598                          ((ngx_http_whitelist_rule_t*)dlc->tmp_wlr->elts)[i].zone,
599                          ((ngx_http_whitelist_rule_t*)dlc->tmp_wlr->elts)[i].name,
600                          ((ngx_http_whitelist_rule_t*)dlc->tmp_wlr->elts)[i].ids->nelts);
601       unsigned int z;
602       for (z = 0; z < ((ngx_http_whitelist_rule_t*)dlc->tmp_wlr->elts)[i].ids->nelts; z++) {
603         ngx_conf_log_error(
604           NGX_LOG_EMERG,
605           cf,
606           0,
607           "id:%d",
608           ((int*)((ngx_http_whitelist_rule_t*)dlc->tmp_wlr->elts)[i].ids->elts)[z]);
609       }
610 #endif
611     }
612     hash_init.key         = &ngx_hash_key_lc;
613     hash_init.pool        = cf->pool;
614     hash_init.temp_pool   = NULL;
615     hash_init.max_size    = 1024;
616     hash_init.bucket_size = 512;
617 
618     if (body_ar) {
619       dlc->wlr_body_hash = (ngx_hash_t*)ngx_pcalloc(cf->pool, sizeof(ngx_hash_t));
620       hash_init.hash     = dlc->wlr_body_hash;
621       hash_init.name     = "wlr_body_hash";
622       if (ngx_hash_init(&hash_init, (ngx_hash_key_t*)body_ar->elts, body_ar->nelts) != NGX_OK) {
623         ngx_conf_log_error(
624           NGX_LOG_EMERG, cf, 0, "$BODY hashtable init failed"); /* LCOV_EXCL_LINE */
625         return (NGX_ERROR);                                     /* LCOV_EXCL_LINE */
626       } else {
627         NX_LOG_DEBUG(_debug_whitelist, NGX_LOG_EMERG, cf, 0, "$BODY hashtable init successed !");
628       }
629     }
630     if (uri_ar) {
631       dlc->wlr_url_hash = (ngx_hash_t*)ngx_pcalloc(cf->pool, sizeof(ngx_hash_t));
632       if (dlc->wlr_url_hash) {
633         hash_init.hash = dlc->wlr_url_hash;
634         hash_init.name = "wlr_url_hash";
635       }
636       if (ngx_hash_init(&hash_init, (ngx_hash_key_t*)uri_ar->elts, uri_ar->nelts) != NGX_OK) {
637         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "$URL hashtable init failed"); /* LCOV_EXCL_LINE */
638         return (NGX_ERROR);                                                     /* LCOV_EXCL_LINE */
639       } else
640         NX_LOG_DEBUG(_debug_whitelist, NGX_LOG_EMERG, cf, 0, "$URL hashtable init successed !");
641     }
642     if (get_ar) {
643       dlc->wlr_args_hash = (ngx_hash_t*)ngx_pcalloc(cf->pool, sizeof(ngx_hash_t));
644       hash_init.hash     = dlc->wlr_args_hash;
645       hash_init.name     = "wlr_args_hash";
646       if (ngx_hash_init(&hash_init, (ngx_hash_key_t*)get_ar->elts, get_ar->nelts) !=
647           NGX_OK) { /* LCOV_EXCL_LINE */
648         ngx_conf_log_error(
649           NGX_LOG_EMERG, cf, 0, "$ARGS hashtable init failed"); /* LCOV_EXCL_LINE */
650         return (NGX_ERROR);
651       } else {
652         NX_LOG_DEBUG(_debug_whitelist,
653                      NGX_LOG_EMERG,
654                      cf,
655                      0,
656                      "$ARGS hashtable init successed %d !",
657                      dlc->wlr_args_hash->size);
658       }
659     }
660     if (headers_ar) {
661       dlc->wlr_headers_hash = (ngx_hash_t*)ngx_pcalloc(cf->pool, sizeof(ngx_hash_t));
662       if (dlc->wlr_headers_hash) {
663         hash_init.hash = dlc->wlr_headers_hash;
664         hash_init.name = "wlr_headers_hash";
665       }
666       if (ngx_hash_init(&hash_init, (ngx_hash_key_t*)headers_ar->elts, headers_ar->nelts) !=
667           NGX_OK) {
668         ngx_conf_log_error(
669           NGX_LOG_EMERG, cf, 0, "$HEADERS hashtable init failed"); /* LCOV_EXCL_LINE */
670         return (NGX_ERROR);                                        /* LCOV_EXCL_LINE */
671       } else {
672         NX_LOG_DEBUG(_debug_whitelist,
673                      NGX_LOG_EMERG,
674                      cf,
675                      0,
676                      "$HEADERS hashtable init successed %d !",
677                      dlc->wlr_headers_hash->size);
678       }
679     }
680   }
681 
682   if (dlc->ignore_ips) {
683     hash_init.key         = &ngx_hash_key;
684     hash_init.pool        = cf->pool;
685     hash_init.temp_pool   = NULL;
686     hash_init.max_size    = 1024 * 128;
687     hash_init.bucket_size = 4096;
688     hash_init.name        = "ignore_ips";
689     hash_init.hash        = dlc->ignore_ips;
690     if (ngx_hash_init(&hash_init, dlc->ignore_ips_ha.keys.elts, dlc->ignore_ips_ha.keys.nelts) !=
691         NGX_OK) {
692       ngx_conf_log_error(
693         NGX_LOG_EMERG, cf, 0, "IPs whitelist hashtable init failed"); // LCOV_EXCL_LINE //
694       return (NGX_ERROR);                                             // LCOV_EXCL_LINE //
695     } else {
696       NX_LOG_DEBUG(_debug_whitelist, NGX_LOG_EMERG, cf, 0, "IPs whitelist hashtable initialized!");
697     }
698   }
699 
700   return (NGX_OK);
701 }
702 
703 /*
704 ** This function will take the whitelist basicrules generated during the
705 *configuration
706 ** parsing phase, and aggregate them to build hashtables according to the
707 *matchzones.
708 **
709 ** As whitelist can be in the form :
710 ** "mz:$URL:bla|$ARGS_VAR:foo"
711 ** "mz:$URL:bla|ARGS"
712 ** "mz:$HEADERS_VAR:Cookie"
713 ** ...
714 **
715 ** So, we will aggregate all the rules that are pointing to the same URL
716 *together,
717 ** as well as rules targetting the same argument name / zone.
718 */
719 
720 ngx_int_t
ngx_http_naxsi_create_hashtables_n(ngx_http_naxsi_loc_conf_t * dlc,ngx_conf_t * cf)721 ngx_http_naxsi_create_hashtables_n(ngx_http_naxsi_loc_conf_t* dlc, ngx_conf_t* cf)
722 {
723   int                        zone, uri_idx, name_idx, ret;
724   ngx_http_rule_t*           curr_r /*, *father_r*/;
725   ngx_http_whitelist_rule_t* father_wlr;
726   ngx_http_rule_t**          rptr;
727   ngx_regex_compile_t*       rgc;
728   char*                      fullname;
729   uint                       i;
730 
731   if (!dlc->whitelist_rules || dlc->whitelist_rules->nelts < 1) {
732     NX_LOG_DEBUG(
733       _debug_whitelist_heavy, NGX_LOG_EMERG, cf, 0, "No whitelist registred, but it's your call.");
734   }
735 
736   if (dlc->whitelist_rules) {
737 
738     NX_LOG_DEBUG(_debug_whitelist_heavy,
739                  NGX_LOG_EMERG,
740                  cf,
741                  0,
742                  "Building whitelist hashtables, %d items in list",
743                  dlc->whitelist_rules->nelts);
744 
745     dlc->tmp_wlr =
746       ngx_array_create(cf->pool, dlc->whitelist_rules->nelts, sizeof(ngx_http_whitelist_rule_t));
747     /* iterate through each stored whitelist rule. */
748     for (i = 0; i < dlc->whitelist_rules->nelts; i++) {
749       uri_idx = name_idx = zone = -1;
750       /*a whitelist is in fact just another basic_rule_t */
751       curr_r = &(((ngx_http_rule_t*)(dlc->whitelist_rules->elts))[i]);
752       NX_LOG_DEBUG(_debug_whitelist_heavy, NGX_LOG_EMERG, cf, 0, "Processing wl %d/%p", i, curr_r);
753 
754       /*no custom location at all means that the rule is disabled */
755       if (!curr_r->br->custom_locations) {
756         NX_LOG_DEBUG(_debug_whitelist_heavy, NGX_LOG_EMERG, cf, 0, "WL %d is a disable rule.", i);
757 
758         if (ngx_http_wlr_push_disabled(cf, dlc, curr_r) == NGX_ERROR)
759           return (NGX_ERROR);
760         continue;
761       }
762       ret = ngx_http_wlr_identify(cf, dlc, curr_r, &zone, &uri_idx, &name_idx);
763       if (ret != NGX_OK) /* LCOV_EXCL_START */
764       {
765         ngx_conf_log_error(
766           NGX_LOG_EMERG, cf, 0, "Following whitelist doesn't target any zone or is incorrect :");
767         if (name_idx != -1)
768           ngx_conf_log_error(NGX_LOG_EMERG,
769                              cf,
770                              0,
771                              "whitelist target name : %V",
772                              &(custloc_array(curr_r->br->custom_locations->elts)[name_idx].target));
773         else
774           ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "whitelist has no target name.");
775         if (uri_idx != -1)
776           ngx_conf_log_error(NGX_LOG_EMERG,
777                              cf,
778                              0,
779                              "whitelist target uri : %V",
780                              &(custloc_array(curr_r->br->custom_locations->elts)[uri_idx].target));
781         else
782           ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "whitelists has no target uri.");
783         return (NGX_ERROR);
784       } /* LCOV_EXCL_STOP */
785       curr_r->br->zone = zone;
786       /*
787       ** Handle regular-expression-matchzone rules :
788       ** Store them in a separate linked list, parsed
789       ** at runtime.
790       */
791       if (curr_r->br->rx_mz == 1) {
792         if (!dlc->rxmz_wlr) {
793           dlc->rxmz_wlr = ngx_array_create(cf->pool, 1, sizeof(ngx_http_rule_t*));
794           if (!dlc->rxmz_wlr)
795             return (NGX_ERROR); /* LCOV_EXCL_LINE */
796         }
797         if (name_idx != -1 &&
798             !custloc_array(curr_r->br->custom_locations->elts)[name_idx].target_rx) {
799           custloc_array(curr_r->br->custom_locations->elts)[name_idx].target_rx =
800             ngx_pcalloc(cf->pool, sizeof(ngx_regex_compile_t));
801           rgc = custloc_array(curr_r->br->custom_locations->elts)[name_idx].target_rx;
802           if (rgc) {
803             rgc->options  = PCRE_CASELESS | PCRE_MULTILINE;
804             rgc->pattern  = custloc_array(curr_r->br->custom_locations->elts)[name_idx].target;
805             rgc->pool     = cf->pool;
806             rgc->err.len  = 0;
807             rgc->err.data = NULL;
808           }
809           // custloc_array(curr_r->br->custom_locations->elts)[name_idx].target;
810           if (ngx_regex_compile(rgc) != NGX_OK)
811             return (NGX_ERROR);
812         }
813         if (uri_idx != -1 &&
814             !custloc_array(curr_r->br->custom_locations->elts)[uri_idx].target_rx) {
815           custloc_array(curr_r->br->custom_locations->elts)[uri_idx].target_rx =
816             ngx_pcalloc(cf->pool, sizeof(ngx_regex_compile_t));
817           rgc = custloc_array(curr_r->br->custom_locations->elts)[uri_idx].target_rx;
818           if (rgc) {
819             rgc->options  = PCRE_CASELESS | PCRE_MULTILINE;
820             rgc->pattern  = custloc_array(curr_r->br->custom_locations->elts)[uri_idx].target;
821             rgc->pool     = cf->pool;
822             rgc->err.len  = 0;
823             rgc->err.data = NULL;
824           }
825           // custloc_array(curr_r->br->custom_locations->elts)[name_idx].target;
826           if (ngx_regex_compile(rgc) != NGX_OK)
827             return (NGX_ERROR);
828         }
829 
830         rptr = ngx_array_push(dlc->rxmz_wlr);
831         if (!rptr)
832           return (NGX_ERROR);
833         *rptr = curr_r;
834         continue;
835       }
836       /*
837       ** Handle static match-zones for hashtables
838       */
839       father_wlr = ngx_http_wlr_find(cf, dlc, curr_r, zone, uri_idx, name_idx, (char**)&fullname);
840       if (!father_wlr) {
841         NX_LOG_DEBUG(
842           _debug_whitelist_heavy, NGX_LOG_EMERG, cf, 0, "creating fresh WL [%s].", fullname);
843 
844         /* creates a new whitelist rule in the right place.
845            setup name and zone, create a new (empty) whitelist_location, as well
846            as a new (empty) id aray. */
847         father_wlr = ngx_array_push(dlc->tmp_wlr);
848         if (!father_wlr)
849           return (NGX_ERROR);
850         memset(father_wlr, 0, sizeof(ngx_http_whitelist_rule_t));
851         father_wlr->name = ngx_pcalloc(cf->pool, sizeof(ngx_str_t));
852         if (!father_wlr->name)
853           return (NGX_ERROR);
854         father_wlr->name->len  = strlen((const char*)fullname);
855         father_wlr->name->data = (unsigned char*)fullname;
856         father_wlr->zone       = zone;
857         /* If there is URI and no name idx, specify it,
858            so that WL system won't get fooled by an argname like an URL */
859         if (uri_idx != -1 && name_idx == -1)
860           father_wlr->uri_only = 1;
861         /* If target_name is present in son, report it. */
862         if (curr_r->br->target_name)
863           father_wlr->target_name = curr_r->br->target_name;
864       }
865       /*merges the two whitelist rules together, including custom_locations. */
866       if (ngx_http_wlr_merge(cf, father_wlr, curr_r) != NGX_OK)
867         return (NGX_ERROR);
868     }
869   }
870 
871   /* and finally, build the hashtables for various zones. */
872   if (ngx_http_wlr_finalize_hashtables(cf, dlc) != NGX_OK)
873     return (NGX_ERROR);
874   /* TODO : Free old whitelist_rules (dlc->whitelist_rules)*/
875   return (NGX_OK);
876 }
877 
878 /*
879   function used for intensive log if dynamic flag is set.
880   Output format :
881   ip=<ip>&server=<server>&uri=<uri>&id=<id>&zone=<zone>&content=<content>
882  */
883 
884 static const char* naxsi_match_zones[] = { "HEADERS",  "URL",     "ARGS", "BODY",
885                                            "FILE_EXT", "UNKNOWN", NULL };
886 
887 void
naxsi_log_offending(ngx_str_t * name,ngx_str_t * val,ngx_http_request_t * req,ngx_http_rule_t * rule,naxsi_match_zone_t zone,ngx_int_t target_name)888 naxsi_log_offending(ngx_str_t*          name,
889                     ngx_str_t*          val,
890                     ngx_http_request_t* req,
891                     ngx_http_rule_t*    rule,
892                     naxsi_match_zone_t  zone,
893                     ngx_int_t           target_name)
894 {
895   ngx_str_t tmp_uri, tmp_val, tmp_name;
896   ngx_str_t empty = ngx_string("");
897 
898   // encode uri
899   tmp_uri.len = req->uri.len +
900                 (2 * ngx_escape_uri(NULL, req->uri.data, req->uri.len, NGX_ESCAPE_URI_COMPONENT));
901   tmp_uri.data = ngx_pcalloc(req->pool, tmp_uri.len + 1);
902   if (tmp_uri.data == NULL)
903     return;
904   ngx_escape_uri(tmp_uri.data, req->uri.data, req->uri.len, NGX_ESCAPE_URI_COMPONENT);
905   // encode val
906   if (val->len <= 0)
907     tmp_val = empty;
908   else {
909     tmp_val.len =
910       val->len + (2 * ngx_escape_uri(NULL, val->data, val->len, NGX_ESCAPE_URI_COMPONENT));
911     tmp_val.data = ngx_pcalloc(req->pool, tmp_val.len + 1);
912     if (tmp_val.data == NULL)
913       return;
914     ngx_escape_uri(tmp_val.data, val->data, val->len, NGX_ESCAPE_URI_COMPONENT);
915   }
916   // encode name
917   if (name->len <= 0)
918     tmp_name = empty;
919   else {
920     tmp_name.len =
921       name->len + (2 * ngx_escape_uri(NULL, name->data, name->len, NGX_ESCAPE_URI_COMPONENT));
922     tmp_name.data = ngx_pcalloc(req->pool, tmp_name.len + 1);
923     if (tmp_name.data == NULL)
924       return;
925     ngx_escape_uri(tmp_name.data, name->data, name->len, NGX_ESCAPE_URI_COMPONENT);
926   }
927 
928   ngx_log_error(NGX_LOG_ERR,
929                 req->connection->log,
930                 0,
931                 "NAXSI_EXLOG: "
932                 "ip=%V&server=%V&uri=%V&id=%d&zone=%s%s&var_name=%V&content=%V",
933                 &(req->connection->addr_text),
934                 &(req->headers_in.server),
935                 &(tmp_uri),
936                 rule->rule_id,
937                 naxsi_match_zones[zone],
938                 target_name ? "|NAME" : "",
939                 &(tmp_name),
940                 &(tmp_val));
941 
942   if (tmp_val.len > 0)
943     ngx_pfree(req->pool, tmp_val.data);
944   if (tmp_name.len > 0)
945     ngx_pfree(req->pool, tmp_name.data);
946   if (tmp_uri.len > 0)
947     ngx_pfree(req->pool, tmp_uri.data);
948 }
949 
950 /*
951 ** Used to check matched rule ID against wl IDs
952 ** Returns 1 if rule is whitelisted, 0 else
953 */
954 int
nx_check_ids(ngx_int_t match_id,ngx_array_t * wl_ids)955 nx_check_ids(ngx_int_t match_id, ngx_array_t* wl_ids)
956 {
957 
958   int          negative = 0;
959   unsigned int i;
960 
961   for (i = 0; i < wl_ids->nelts; i++) {
962     if (((ngx_int_t*)wl_ids->elts)[i] == match_id)
963       return (1);
964     if (((ngx_int_t*)wl_ids->elts)[i] == 0)
965       return (1);
966     /* manage negative whitelists, except for internal rules */
967     if (((ngx_int_t*)wl_ids->elts)[i] < 0 && match_id >= 1000) {
968       negative = 1;
969       /* negative wl excludes this one.*/
970       if (match_id == -((ngx_int_t*)wl_ids->elts)[i]) {
971         return (0);
972       }
973     }
974   }
975   return (negative == 1);
976 }
977