diff --git a/.github/nginx/docs/403.html b/.github/nginx/docs/403.html new file mode 100644 index 0000000..237f5a0 --- /dev/null +++ b/.github/nginx/docs/403.html @@ -0,0 +1,10 @@ + + +403 + + + +Forbidden 403 - custom error page. + + + diff --git a/.github/nginx/nginx.conf.redir b/.github/nginx/nginx.conf.redir new file mode 100644 index 0000000..698951a --- /dev/null +++ b/.github/nginx/nginx.conf.redir @@ -0,0 +1,90 @@ +user www-data; +worker_processes auto; +pid /run/nginx.pid; +worker_cpu_affinity auto; + +#working_directory /tmp/cores/; +worker_rlimit_core 2000M; +debug_points abort; + +#load_module /usr/local/nginx/modules/ngx_http_modsecurity_module.so; + +events { + worker_connections 768; +# multi_accept on; +# use epoll; +} + +worker_rlimit_nofile 33268; + +#daemon off; +#master_process off; + +http { + + ## + # Basic Settings + ## + + types_hash_max_size 2048; + + server_names_hash_bucket_size 64; + + include mime.types; + default_type application/octet-stream; + + ## + # Logging Settings + ## + + #access_log /dev/stdout; + #error_log /dev/stdout info; + access_log /usr/local/nginx/logs/access.log; + error_log /usr/local/nginx/logs/error.log info; + + server_tokens off; + + proxy_hide_header X-Powered-By; + + modsecurity on; + + server { + listen 80; + server_name modsectest1; + + modsecurity on; + modsecurity_rules_file /home/runner/work/ModSecurity-nginx/ModSecurity-nginx/ModSecurity-nginx/.github/nginx/modsecurity.conf; + root /usr/local/nginx/html/; + + error_page 403 /403.html; + + location /403.html { + internal; + } + + location / { + try_files $uri /index.html; + } + } + + server { + listen 80; + server_name modsectest2; + + modsecurity on; + modsecurity_rules_file /home/runner/work/ModSecurity-nginx/ModSecurity-nginx/ModSecurity-nginx/.github/nginx/modsecurity.conf; + root /usr/local/nginx/html/; + + error_page 403 /403.html; + + location /403.html { + internal; + } + + location / { + try_files $uri /index.html; + } + } + +} + diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dc1bc1b..9841dd1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -140,6 +140,78 @@ jobs: echo "FAIL" exit 1 fi + - name: Start Nginx with redir + run: | + sudo killall nginx + sudo /usr/local/nginx/sbin/nginx -c /home/runner/work/ModSecurity-nginx/ModSecurity-nginx/ModSecurity-nginx/.github/nginx/nginx.conf.redir + - name: Run attack test vhost 1 + run: | + status=$(curl -sSo /dev/null -w %{http_code} -I -X GET -H "Host: modsectest1" "http://localhost/?q=attack") + if [ "${status}" == "403" ]; then + echo "OK" + else + echo "FAIL" + exit 1 + fi + - name: Run non-attack test vhost 1 (redir config) + run: | + status=$(curl -sSo /dev/null -w %{http_code} -I -X GET -H "Host: modsectest1" "http://localhost/?q=1") + if [ "${status}" == "200" ]; then + echo "OK" + else + echo "FAIL" + exit 1 + fi + - name: Run attack test vhost 2 (redir config) + run: | + status=$(curl -sSo /dev/null -w %{http_code} -I -X GET -H "Host: modsectest2" "http://localhost/?q=attack") + if [ "${status}" == "403" ]; then + echo "OK" + else + echo "FAIL" + exit 1 + fi + - name: Run non-attack test vhost 2 (redir config) + run: | + status=$(curl -sSo /dev/null -w %{http_code} -I -X GET -H "Host: modsectest2" "http://localhost/?q=1") + if [ "${status}" == "200" ]; then + echo "OK" + else + echo "FAIL" + exit 1 + fi + - name: Run file consistency check 1 (redir config) + run: | + curl -sS "http://localhost/data50k.json" --output data50k.json + if [ -f data50k.json ]; then + diff data50k.json /usr/local/nginx/html/data50k.json > /dev/null + if [ $? -eq 0 ]; then + ls -l data50k.json /usr/local/nginx/html/data50k.json + echo "OK" + else + echo "FAIL" + exit 2 + fi + else + echo "FAIL" + exit 1 + fi + - name: Run file consistency check 2 (redir config) + run: | + curl -sS "http://localhost/plugged.png" --output plugged.png + if [ -f plugged.png ]; then + diff plugged.png /usr/local/nginx/html/plugged.png > /dev/null + if [ $? -eq 0 ]; then + ls -l plugged.png /usr/local/nginx/html/plugged.png + echo "OK" + else + echo "FAIL" + exit 2 + fi + else + echo "FAIL" + exit 1 + fi build-windows: diff --git a/src/ngx_http_modsecurity_body_filter.c b/src/ngx_http_modsecurity_body_filter.c index 86bccc7..0c28e3c 100644 --- a/src/ngx_http_modsecurity_body_filter.c +++ b/src/ngx_http_modsecurity_body_filter.c @@ -50,7 +50,7 @@ ngx_http_modsecurity_body_filter(ngx_http_request_t *r, ngx_chain_t *in) return ngx_http_next_body_filter(r, in); } - ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity_module); + ctx = ngx_http_modsecurity_get_module_ctx(r); dd("body filter, recovering ctx: %p", ctx); diff --git a/src/ngx_http_modsecurity_common.h b/src/ngx_http_modsecurity_common.h index 11fdc2d..cde48a5 100644 --- a/src/ngx_http_modsecurity_common.h +++ b/src/ngx_http_modsecurity_common.h @@ -99,6 +99,7 @@ typedef struct { unsigned processed:1; unsigned logged:1; unsigned intervention_triggered:1; + unsigned request_body_processed:1; } ngx_http_modsecurity_ctx_t; @@ -139,6 +140,7 @@ extern ngx_module_t ngx_http_modsecurity_module; /* ngx_http_modsecurity_module.c */ int ngx_http_modsecurity_process_intervention (Transaction *transaction, ngx_http_request_t *r, ngx_int_t early_log); ngx_http_modsecurity_ctx_t *ngx_http_modsecurity_create_ctx(ngx_http_request_t *r); +ngx_http_modsecurity_ctx_t *ngx_http_modsecurity_get_module_ctx(ngx_http_request_t *r); char *ngx_str_to_char(ngx_str_t a, ngx_pool_t *p); #if (NGX_PCRE2) #define ngx_http_modsecurity_pcre_malloc_init(x) NULL diff --git a/src/ngx_http_modsecurity_header_filter.c b/src/ngx_http_modsecurity_header_filter.c index 257e7fd..03b8764 100644 --- a/src/ngx_http_modsecurity_header_filter.c +++ b/src/ngx_http_modsecurity_header_filter.c @@ -109,7 +109,7 @@ ngx_http_modsecurity_store_ctx_header(ngx_http_request_t *r, ngx_str_t *name, ng ngx_http_modsecurity_conf_t *mcf; ngx_http_modsecurity_header_t *hdr; - ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity_module); + ctx = ngx_http_modsecurity_get_module_ctx(r); if (ctx == NULL || ctx->sanity_headers_out == NULL) { return NGX_ERROR; } @@ -152,7 +152,7 @@ ngx_http_modsecurity_resolv_header_server(ngx_http_request_t *r, ngx_str_t name, ngx_str_t value; clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); - ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity_module); + ctx = ngx_http_modsecurity_get_module_ctx(r); if (r->headers_out.server == NULL) { if (clcf->server_tokens) { @@ -186,7 +186,7 @@ ngx_http_modsecurity_resolv_header_date(ngx_http_request_t *r, ngx_str_t name, o ngx_http_modsecurity_ctx_t *ctx = NULL; ngx_str_t date; - ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity_module); + ctx = ngx_http_modsecurity_get_module_ctx(r); if (r->headers_out.date == NULL) { date.data = ngx_cached_http_time.data; @@ -216,7 +216,7 @@ ngx_http_modsecurity_resolv_header_content_length(ngx_http_request_t *r, ngx_str ngx_str_t value; char buf[NGX_INT64_LEN+2]; - ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity_module); + ctx = ngx_http_modsecurity_get_module_ctx(r); if (r->headers_out.content_length_n > 0) { @@ -243,7 +243,7 @@ ngx_http_modsecurity_resolv_header_content_type(ngx_http_request_t *r, ngx_str_t { ngx_http_modsecurity_ctx_t *ctx = NULL; - ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity_module); + ctx = ngx_http_modsecurity_get_module_ctx(r); if (r->headers_out.content_type.len > 0) { @@ -270,7 +270,7 @@ ngx_http_modsecurity_resolv_header_last_modified(ngx_http_request_t *r, ngx_str_ u_char buf[1024], *p; ngx_str_t value; - ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity_module); + ctx = ngx_http_modsecurity_get_module_ctx(r); if (r->headers_out.last_modified_time == -1) { return 1; @@ -302,7 +302,7 @@ ngx_http_modsecurity_resolv_header_connection(ngx_http_request_t *r, ngx_str_t n ngx_str_t value; clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); - ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity_module); + ctx = ngx_http_modsecurity_get_module_ctx(r); if (r->headers_out.status == NGX_HTTP_SWITCHING_PROTOCOLS) { connection = "upgrade"; @@ -353,7 +353,7 @@ ngx_http_modsecurity_resolv_header_transfer_encoding(ngx_http_request_t *r, ngx_ if (r->chunked) { ngx_str_t value = ngx_string("chunked"); - ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity_module); + ctx = ngx_http_modsecurity_get_module_ctx(r); #if defined(MODSECURITY_SANITY_CHECKS) && (MODSECURITY_SANITY_CHECKS) ngx_http_modsecurity_store_ctx_header(r, &name, &value); @@ -380,7 +380,7 @@ ngx_http_modsecurity_resolv_header_vary(ngx_http_request_t *r, ngx_str_t name, o if (r->gzip_vary && clcf->gzip_vary) { ngx_str_t value = ngx_string("Accept-Encoding"); - ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity_module); + ctx = ngx_http_modsecurity_get_module_ctx(r); #if defined(MODSECURITY_SANITY_CHECKS) && (MODSECURITY_SANITY_CHECKS) ngx_http_modsecurity_store_ctx_header(r, &name, &value); @@ -422,7 +422,7 @@ ngx_http_modsecurity_header_filter(ngx_http_request_t *r) /* XXX: if NOT_MODIFIED, do we need to process it at all? see xslt_header_filter() */ - ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity_module); + ctx = ngx_http_modsecurity_get_module_ctx(r); dd("header filter, recovering ctx: %p", ctx); diff --git a/src/ngx_http_modsecurity_log.c b/src/ngx_http_modsecurity_log.c index 167b2d3..094685c 100644 --- a/src/ngx_http_modsecurity_log.c +++ b/src/ngx_http_modsecurity_log.c @@ -41,17 +41,9 @@ ngx_http_modsecurity_log_handler(ngx_http_request_t *r) { ngx_pool_t *old_pool; ngx_http_modsecurity_ctx_t *ctx; - ngx_http_modsecurity_conf_t *mcf; dd("catching a new _log_ phase handler"); - mcf = ngx_http_get_module_loc_conf(r, ngx_http_modsecurity_module); - if (mcf == NULL || mcf->enable != 1) - { - dd("ModSecurity not enabled... returning"); - return NGX_OK; - } - /* if (r->method != NGX_HTTP_GET && r->method != NGX_HTTP_POST && r->method != NGX_HTTP_HEAD) { @@ -60,13 +52,13 @@ ngx_http_modsecurity_log_handler(ngx_http_request_t *r) return NGX_OK; } */ - ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity_module); + ctx = ngx_http_modsecurity_get_module_ctx(r); dd("recovering ctx: %p", ctx); if (ctx == NULL) { - dd("something really bad happened here. returning NGX_ERROR"); - return NGX_ERROR; + dd("ModSecurity not enabled or error occurred"); + return NGX_OK; } if (ctx->logged) { diff --git a/src/ngx_http_modsecurity_module.c b/src/ngx_http_modsecurity_module.c index c90b3f6..e8a5f4b 100644 --- a/src/ngx_http_modsecurity_module.c +++ b/src/ngx_http_modsecurity_module.c @@ -149,7 +149,7 @@ ngx_http_modsecurity_process_intervention (Transaction *transaction, ngx_http_re dd("processing intervention"); - ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity_module); + ctx = ngx_http_modsecurity_get_module_ctx(r); if (ctx == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; @@ -313,6 +313,27 @@ ngx_http_modsecurity_create_ctx(ngx_http_request_t *r) return ctx; } +ngx_inline ngx_http_modsecurity_ctx_t * +ngx_http_modsecurity_get_module_ctx(ngx_http_request_t *r) +{ + ngx_http_modsecurity_ctx_t *ctx; + ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity_module); + if (ctx == NULL) { + /* + * refer /src/http/modules/ngx_http_realip_module.c + * if module context was reset, the original address + * can still be found in the cleanup handler + */ + ngx_pool_cleanup_t *cln; + for (cln = r->pool->cleanup; cln; cln = cln->next) { + if (cln->handler == ngx_http_modsecurity_cleanup) { + ctx = cln->data; + break; + } + } + } + return ctx; +} char * ngx_conf_set_rules(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) diff --git a/src/ngx_http_modsecurity_pre_access.c b/src/ngx_http_modsecurity_pre_access.c index ea5c021..75ac45d 100644 --- a/src/ngx_http_modsecurity_pre_access.c +++ b/src/ngx_http_modsecurity_pre_access.c @@ -27,7 +27,7 @@ ngx_http_modsecurity_request_read(ngx_http_request_t *r) { ngx_http_modsecurity_ctx_t *ctx; - ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity_module); + ctx = ngx_http_modsecurity_get_module_ctx(r); #if defined(nginx_version) && nginx_version >= 8011 r->main->count--; @@ -70,7 +70,7 @@ ngx_http_modsecurity_pre_access_handler(ngx_http_request_t *r) } */ - ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity_module); + ctx = ngx_http_modsecurity_get_module_ctx(r); dd("recovering ctx: %p", ctx); @@ -80,6 +80,11 @@ ngx_http_modsecurity_pre_access_handler(ngx_http_request_t *r) return NGX_HTTP_INTERNAL_SERVER_ERROR; } + if (ctx->request_body_processed) { + // should we use r->internal or r->filter_finalize? + return NGX_DECLINED; + } + if (ctx->intervention_triggered) { return NGX_DECLINED; } @@ -212,6 +217,7 @@ ngx_http_modsecurity_pre_access_handler(ngx_http_request_t *r) old_pool = ngx_http_modsecurity_pcre_malloc_init(r->pool); msc_process_request_body(ctx->modsec_transaction); + ctx->request_body_processed = 1; ngx_http_modsecurity_pcre_malloc_done(old_pool); ret = ngx_http_modsecurity_process_intervention(ctx->modsec_transaction, r, 0); diff --git a/src/ngx_http_modsecurity_rewrite.c b/src/ngx_http_modsecurity_rewrite.c index 926cf70..eaff1cc 100644 --- a/src/ngx_http_modsecurity_rewrite.c +++ b/src/ngx_http_modsecurity_rewrite.c @@ -46,7 +46,7 @@ ngx_http_modsecurity_rewrite_handler(ngx_http_request_t *r) dd("catching a new _rewrite_ phase handler"); - ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity_module); + ctx = ngx_http_modsecurity_get_module_ctx(r); dd("recovering ctx: %p", ctx);