[FALSE-POSITIVE] CVE-2025-29927
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 -niThe 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: 0http(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: 0Both 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:
- 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:
- 200This 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:
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:
- 200Source: projectdiscovery/nuclei-templates