Written by: Rachid.A
Translate: Yewlne
01 Translation text
Recently, I teamed up with Yasser Allam, who goes by the pseudonym inzo_, to conduct research. After discussing several potential targets, we decided to focus on Next.js (which has 130,000 Stars on GitHub and currently over 9.4 million downloads per week). This is a framework I am very familiar with, and I have had a wonderful creative experience with it, as my previous research results have shown. Therefore, the “we” in this article naturally refers to both of us.
Next.js is a full-featured JavaScript framework based on React, with a wealth of features — an ideal place to delve into the details. With faith, curiosity, and resilience, we embark on a journey to explore those little-known corners, searching for the treasures hidden within.
Not long after, we discovered a significant issue in the middleware. Its impact is widespread, affecting all versions, and exploiting this vulnerability requires no prerequisites - we will soon provide a detailed demonstration.
Catalog
Next.js Middleware
Authorization Artifact: Treasure-Level Old Code
Execution order and middlewareInfo.name
Authorization Artifact: Yesterday has become poetry, and today is even more worthwhile.
/src directory
Maximum recursion depth
Exploit
Bypass authorization/rewrite
Bypassing CSP
Achieving DoS through cache poisoning (What?)
Clarification
Security Announcement - CVE-2025-29927
Disclaimer
Conclusion
Next.js Middleware
Middleware allows you to execute code before the request is completed. You can then modify the response content based on the incoming request, either by rewriting, redirecting, modifying the request or response headers, or by directly returning the response (excerpt from the Next.js documentation).
As a complete framework, Next.js has its own middleware - an important and widely used feature. Its application scenarios are numerous, among which the most important include:
Path rewriting
Server-side redirects
Add header information (such as CSP, etc.) to the response elements.
The most important things are: Authentication and Authorization
A common use of middleware is for authorization, which involves protecting specific paths based on certain conditions.
Authentication and Authorization: Ensure user identity and check session cookies (Next.js documentation) before granting access to specific pages or API routes.
Example: When a user attempts to access /dashboard/admin, the request will first go through middleware, which will check if the user’s session cookie is valid and if they have the necessary permissions. If the validation passes, the middleware will forward the request; otherwise, the middleware will redirect the user to the login page:
Authorization Artifact: Treasure-Level Old Code
As a great person once said, “talk is cheap, show me the bug”; let’s avoid excessive narration and get straight to the point. While browsing the old version (v12.0.7) of the framework, we found this piece of code:
When a Next.js application uses middleware, the runMiddleware function is called. In addition to its main functionality, this function also retrieves the value of the x-middleware-subrequest header and uses it to determine whether middleware should be applied. The header value is split into a list using a colon (:) as a separator, and then it checks whether this list contains the value of middlewareInfo.name. This means that if we add the x-middleware-subrequest header with the correct value in the request, the middleware—regardless of its purpose—will be completely ignored, the request will be forwarded through NextResponse.next(), and the path to the original destination will be completed without any influence from the middleware. This header and its value act like a ‘universal key’ that can bypass all rules. At this point, we have realized that we have discovered an amazing issue, and we need to complete the last few pieces of the puzzle.
For our “universal key” to be effective, its value must include middlewareInfo.name, but what exactly is this value?
Execution order and middlewareInfo.name
The value of middlewareInfo.name is very easy to infer; it is simply the path where the middleware is located. To understand this, we need to have a brief understanding of how middleware was configured in previous versions.
First, before version 12.2 - in which the middleware convention changed - the file must be named _middleware.ts. Additionally, the app router was only introduced in version 13 of Next.js. The only router that existed at that time was the pages router, so this file must be placed within the pages folder (specific to the router).
With this information, we can infer the exact path of the middleware, thereby guessing the value of the x-middleware-subrequest header. This value is composed solely of the directory name (i.e., the unique router name that existed at the time) and the filename, following the naming convention that starts with an underscore at that time:
x-middleware-subrequest: pages/_middleware
When we try to bypass the middleware that is configured to systematically redirect access attempts from /dashboard/team/admin to /dashboard:
We succeeded, we have infiltrated ⚔️
We can now completely bypass middleware, thus circumventing any protection systems based on it, the most typical being authorization, just like in the example above. This discovery is quite astonishing, but there are other points to consider.
Version 12.2 allowed nested routes to place one or more _middleware files at any position in the directory tree (starting from the pages folder), and they have an execution order, as seen in the old document screenshots retrieved from the Web Archive:
What does this mean for our exploitation?
Possibility = Number of levels in the path
Therefore, to access /dashboard/panel/admin (protected by middleware), the value of middlewareInfo.name can be one of three possibilities, and correspondingly, the value of x-middleware-subrequest can also be one of three possibilities:
pages/_middleware
or
pages/dashboard/_middleware
or
pages/dashboard/panel/_middleware
Authorization Artifact: Yesterday has become poetry, and today is even more worthwhile.
So far, we believe that only versions prior to version 13 are vulnerable to attack, as the middleware has been moved in the source code, and we have not yet covered all aspects of it. We speculate that the maintainers must have noticed this vulnerability and fixed it before the significant changes in version 13, so we reported this vulnerability to the framework maintainers and continued our research.
What surprised us greatly is that two days after the initial discovery, we found that all versions of Next.js—from version 11.1.4 onwards—have vulnerabilities! The code is no longer in the same place, and the logic for exploiting the vulnerabilities has also changed slightly.
As mentioned earlier, starting from version 12.2, the file will no longer contain an underscore and must be simply named middleware.ts. Additionally, it is no longer located in the pages folder (which is convenient for us, as version 13 introduced the app router, which would have doubled the possibilities).
With that in mind, the payload for the first versions starting with version 12.2 is very simple:
With this in mind, the payload for the first version starting from version 12.2 is very simple:
x-middleware-subrequest: middleware
/src directory
It also needs to be taken into account that Next.js provides the possibility of creating a /src directory:
(Next.js documentation) As an alternative method to having a special Next.js app or pages directory in the project root, Next.js also supports the common pattern of placing application code under the src directory. (Next.js documentation)
In this case, the payload will be:
x-middleware-subrequest: src/middleware
Therefore, regardless of how many levels there are in the path, there are only two possibilities in total. This simplifies the difficulty of exploiting vulnerabilities in the relevant versions.
In the latest version, there has been a slight change again (we guarantee, for the last time).
Maximum recursion depth
In the updated version, the logic has changed slightly, please take a look at this piece of code:
v15.1.7
As before, the system will retrieve the value of the x-middleware-subrequest header and form a list using a colon as the delimiter. However, this time, the conditions for direct request forwarding—namely, ignoring middleware rules—are different:
The value of the constant depth must be greater than or equal to the value of the constant MAX_RECURSION_DEPTH (i.e., 5). During the assignment process, whenever a value in the list subrequests (i.e., header values separated by :) equals params.name (i.e., the path of the middleware), the constant depth will increase by 1. As mentioned earlier, there are only two possibilities here: middleware or src/middleware.
Therefore, to bypass the middleware, we only need to add the following header/value in the request:
x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware
or
x-middleware-subrequest: src/middleware:src/middleware:src/middleware:src/middleware:src/middleware
What was this code originally used for?
This piece of code seems to be intended to prevent recursive requests from falling into an infinite loop.
Exploit
Since we know you like this kind of content, here are some real cases from the Bug Bounty Program.
Bypass authorization/rewrite
In this example, when we attempt to access /admin/login, we receive a 404 response. From the response headers, it can be seen that the middleware executed path rewriting to prevent unauthorized or inappropriate users from accessing it:
But using our authorized tool:
We can access the endpoint without any barriers, and the middleware is completely ignored. Target Next.js version: 15.1.7
Bypassing CSP
This time the website uses middleware to set up - among other features - CSP and cookies:
Let’s bypass it:
Target next.js version: 15.0.3
Note: Please pay attention to the differences in the payloads of the two targets, one of which uses the src/ directory while the other does not.
Achieving DoS through cache poisoning (What?)
Yes, it is also possible to achieve cache poisoning DoS attacks through this vulnerability. This is obviously not what we are looking for first, but if there are no sensitive paths protected and no more interesting exploit points, then certain situations may lead to cache poisoning denial of service (CPDoS):
Suppose a website rewrites user paths based on the user’s geographical location, adding (/en, /fr, etc.), and does not provide pages or resources at the root path (/). If we bypass the middleware, we will skip the rewrite and ultimately reach the root page. Since the developer did not intend for users to access the root page, no corresponding page has been provided, resulting in a 404 (or possibly a 500 depending on the rewrite configuration/type).
If the website uses a caching/CDN system, it may forcibly cache 404 responses, making the page unavailable and severely impacting site availability.
Clarification
Since the release of the security announcement, we have received inquiries from some individuals who are concerned about the security of their applications and do not fully understand the scope of the attacks. It is important to clarify that the vulnerable element is the middleware. If you are not using middleware (or at least not using it for sensitive purposes), then there is no need to worry (however, please check the DoS aspects mentioned above), because bypassing the middleware does not bypass any actual security mechanisms.
Otherwise, the consequences could be disastrous, and we recommend that you promptly implement the guidelines in the security notice.
Security Announcement - CVE-2025-29927
Patch
This issue has been fixed in Next.js 15.2.3.
This issue has been fixed in 14.2.25 for Next.js 14.x.
For Next.js versions 11.1.4 to 13.5.6, we recommend referring to the following solutions.
Solution
If you cannot upgrade to a secure version, we recommend that you block external user requests containing the x-middleware-subrequest request header from accessing your Next.js application.
Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N (Severity: 9.1/10, Critical)
More information
At the time of writing, applications deployed on Vercel and Netlify are clearly no longer affected by this vulnerability (Update: Due to a large number of false positives, Cloudflare has adjusted the rule to only take effect when users actively enable it — these false positives failed to effectively distinguish between requests from legitimate users and those from potential attackers).
Disclaimer
This research is published solely for educational purposes, aimed at helping developers understand the root causes of issues, or providing inspiration for researchers / vulnerability hunters in their future work. This article serves as supplementary material to the security announcement, providing further explanations and clarifications regarding the nature of the vulnerability – as the request headers that led to the vulnerability (along with the related submission differences) have already been disclosed in the announcement.
We explicitly state that we do not support any unethical use of this document.
Conclusion
As emphasized in this article, this vulnerability has existed in the Next.js source code for several years, evolving with the middleware and its versions. Any software can have serious vulnerabilities, but when it affects one of the most popular frameworks, it becomes particularly dangerous and can have severe consequences for the broader ecosystem. As mentioned earlier, at the time of writing this article, Next.js has nearly 10 million downloads per week. It is widely used in critical areas ranging from banking services to blockchain. The risks increase when vulnerabilities affect mature features that users rely on, such as authorization and authentication.
The Vercel team spent a few days resolving this vulnerability, but it is worth noting that once they realized the issue, the fix was submitted and merged into the new version within a few hours (including backporting).
Timeline :
February 27, 2025: A vulnerability was reported to the maintainers (at that time we believed that only versions 12.0.0 to 12.0.7 were affected, and this was noted in the report).
March 1, 2025: The second email was sent, indicating that vulnerabilities actually exist in all versions, including the latest stable version.
March 5, 2025: Received a preliminary response from the Vercel team stating that version 12.x is no longer maintained (they may not have read the security announcement template attached in our second email and did not notice that all versions are affected).
March 5, 2025: Resending the email, please have the team review the second email and the security announcement template as soon as possible.
March 11, 2025: Send another email to confirm whether the new information has been adopted.
March 17, 2025: Received a response from the Vercel team, confirming that the relevant information has been adopted.
March 18, 2025: Received an email from the Vercel team indicating that the report has been accepted and the patch has been completed. A few hours later, version 15.2.3, which includes the fix, was released (along with the backport fix).
March 21, 2025: Security announcement officially released.
Overall, the process of searching for zero-day vulnerabilities is only exciting and adrenaline-pumping at the moment a clue is discovered; the rest of the time feels like a journey filled with uncertainty – it can bring knowledge to the curious but seems particularly long for those lacking patience. Don’t hesitate, teaming up is much easier than crossing the desert alone.