daemon off; worker_processes 1; error_log logs/error.log info; pid tmp/nginx.pid; worker_rlimit_core 500M; working_directory /app/tmp; events { worker_connections 1024; } http { access_log off; client_body_temp_path /app/tmp; proxy_temp_path /app/tmp; fastcgi_temp_path /app/tmp; uwsgi_temp_path /app/tmp; scgi_temp_path /app/tmp; # Local proxy backend (backend.py) — /internal and /spray proxy here. # The deliberate response delay keeps the request alive long enough for # POSTed bodies to linger in the request pool (the spray window). upstream backend { server 127.0.0.1:19323; } server { # Listen on all container interfaces so Docker's port-forward proxy can # reach it. Host-facing exposure is still pinned to 127.0.0.1 in # docker-compose.yml (`127.0.0.1:19321:19321`); nothing is reachable # from outside the host. listen 19321; # Pool sizing reproduces the upstream PoC's heap layout so the # adjacent-pool overflow lands deterministically. request_pool_size 7920; connection_pool_size 4096; client_header_buffer_size 2048; # --- The three trigger ingredients in ONE location (CVE-2026-42945) --- # 1. unnamed PCRE capture (.*) in the rewrite source pattern # 2. literal '?' in the replacement -> sets e->is_args = 1 # 3. a later directive referencing the capture ($1) # Length pass sees is_args=0 (raw length); copy pass sees is_args=1 and # re-escapes $1 via ngx_escape_uri(NGX_ESCAPE_ARGS), overflowing the # undersized heap buffer. location ~ ^/api/(.*)$ { rewrite ^/api/(.*)$ /internal?migrated=true; set $original_endpoint $1; } location /internal { internal; proxy_pass http://backend; proxy_read_timeout 60s; } # --- Spray endpoint --- # POST bodies are buffered into the request pool (NUL bytes allowed, # unlike URI bytes), letting the attacker plant a fake # ngx_pool_cleanup_s struct + command string at known heap offsets. location /spray { client_body_in_single_buffer on; proxy_pass http://backend; proxy_read_timeout 60s; } location / { return 200 "ok\n"; } } }