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