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