[FALSE-POSITIVE] CVE-2025-29927

Author: michaelsc44Created Sep 16, 2026Updated Sep 16, 2026

Template IDs or paths

http/cves/2025/CVE-2025-29927.yaml

Nuclei version

v3.11.1

Environment

Debian GNU/Linux 12 (bookworm), nuclei v3.11.1, templates auto-updated on the run. Single template run against one target.

Steps to reproduce

Run the template against any Next.js origin whose auth middleware answers / with a 307 redirect to a login route and an empty body. That is the default behavior of NextResponse.redirect(), so it is very common.

nuclei -u https://<target> -t http/cves/2025/CVE-2025-29927.yaml -debug -ni

The template reports a critical match even though the target is patched and no bypass occurs:

[CVE-2025-29927:word-1] [http] [critical] https://<target>

The matcher name in the output is word-1, and the response status on the "bypass" request is 307, not 200.

Relevant dumped responses

http(1), the baseline request:

GET / HTTP/1.1
Host: <target>
X-Nextjs-Data: 1

HTTP/1.1 307 Temporary Redirect
Content-Type: text/html
Server: Google Frontend
X-Nextjs-Redirect: https://<target>/login
Content-Length: 0

http(2), the crafted X-Middleware-Subrequest request that is supposed to prove the bypass:

GET / HTTP/1.1
Host: <target>
X-Middleware-Subrequest: src/middleware:nowaf:src/middleware:src/middleware:src/middleware:src/middleware:middleware:middleware:nowaf:middleware:middleware:middleware:pages/_middleware
X-Nextjs-Data: 1

HTTP/1.1 307 Temporary Redirect
Content-Type: text/html
Server: Google Frontend
X-Nextjs-Redirect: https://<target>/login
Content-Length: 0

Both requests return the same 307 redirect to /login with an empty body. The crafted header changes nothing, so no bypass happened, yet the template still fires.

Root cause

The second request block (the bypass verification step) is missing matchers-condition: and, so it falls back to the default of or:

yaml
- raw:
    - |
      GET / HTTP/1.1
      Host: {{Hostname}}
      X-Nextjs-Data: 1
      X-Middleware-Subrequest: ...
  matchers:
    - type: word
      part: body
      words:
        - "Request Rejected"
        - "The requested URL was rejected"
        - "Your support ID is"
        - "Access Denied"
        - "Request Blocked"
      negative: true
    - type: status
      status:
        - 200

This step is meant to confirm the bypass by requiring a 200 status AND the absence of a WAF/block page. Under the default or, either matcher alone is enough. When the target is not vulnerable, the crafted header is ignored and the app returns its normal 307 redirect with an empty body. The empty body contains none of the block strings, so the negative word matcher returns true and the step matches on its own, even though the status is 307 rather than 200.

http(1) legitimately matches on any Next.js middleware that redirects unauthenticated requests, so base_check() returns true and a critical finding is emitted with no bypass present. This over-reports on essentially every Next.js deployment that uses middleware for auth redirects, because an empty-body redirect is the standard response.

For contrast, the first request block sets matchers-condition: and explicitly, and the fifth block uses condition: and. Only the second block omits it.

Suggested fix

Add matchers-condition: and to the second request block so a match requires both the 200 status and the absence of block strings, matching the intent of the step:

yaml
matchers-condition: and
matchers:
  - type: word
    part: body
    words:
      - "Request Rejected"
      - "The requested URL was rejected"
      - "Your support ID is"
      - "Access Denied"
      - "Request Blocked"
    negative: true
  - type: status
    status:
      - 200

Source: projectdiscovery/nuclei-templates