# NextJS
{{#include ../../banners/hacktricks-training.md}}
## Next.js 应用程序的一般架构
### 典型文件结构
一个标准的 Next.js 项目遵循特定的文件和目录结构,以便于其功能,如路由、API 端点和静态资产管理。以下是一个典型的布局:
```lua
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 请求的无服务器函数。这些路由替代了传统的 `pages/api` 目录。
- **app/components/:** 存放可重用的 React 组件,可在不同页面和布局中使用。
- **app/styles/:** 包含全局 CSS 文件和用于组件范围样式的 CSS 模块。
- **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 版本中路由的基石。它利用文件系统来定义路由,使路由管理直观且可扩展。
处理根路径 /
**文件结构:**
```arduino
my-nextjs-app/
├── app/
│ ├── layout.tsx
│ └── page.tsx
├── public/
├── next.config.js
└── ...
```
**关键文件:**
- **`app/page.tsx`**:处理对根路径 `/` 的请求。
- **`app/layout.tsx`**:定义应用程序的布局,包裹所有页面。
**实现:**
```tsx
tsxCopy code// app/page.tsx
export default function HomePage() {
return (
Welcome to the Home Page!
This is the root route.
);
}
```
**解释:**
- **路由定义:** `app` 目录下的 `page.tsx` 文件对应于 `/` 路由。
- **渲染:** 该组件渲染主页的内容。
- **布局集成:** `HomePage` 组件被 `layout.tsx` 包裹,可以包含头部、底部和其他公共元素。
处理其他静态路径
**示例:`/about` 路由**
**文件结构:**
```arduino
arduinoCopy codemy-nextjs-app/
├── app/
│ ├── about/
│ │ └── page.tsx
│ ├── layout.tsx
│ └── page.tsx
├── public/
├── next.config.js
└── ...
```
**实施:**
```tsx
// app/about/page.tsx
export default function AboutPage() {
return (
About Us
Learn more about our mission and values.
)
}
```
**解释:**
- **路由定义:** `about` 文件夹中的 `page.tsx` 文件对应于 `/about` 路由。
- **渲染:** 该组件渲染关于页面的内容。
动态路由
动态路由允许处理具有可变段的路径,使应用程序能够根据参数(如 ID、slug 等)显示内容。
**示例:`/posts/[id]` 路由**
**文件结构:**
```arduino
arduinoCopy codemy-nextjs-app/
├── app/
│ ├── posts/
│ │ └── [id]/
│ │ └── page.tsx
│ ├── layout.tsx
│ └── page.tsx
├── public/
├── next.config.js
└── ...
```
**实施:**
```tsx
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 (
Post #{id}
This is the content of post {id}.
);
}
```
**解释:**
- **动态段:** `[id]` 表示路由中的动态段,从 URL 中捕获 `id` 参数。
- **访问参数:** `params` 对象包含动态参数,可以在组件内访问。
- **路由匹配:** 任何匹配 `/posts/*` 的路径,例如 `/posts/1`、`/posts/abc` 等,将由此组件处理。
嵌套路由
Next.js 支持嵌套路由,允许创建与目录结构相对应的层次路由结构。
**示例:`/dashboard/settings/profile` 路由**
**文件结构:**
```arduino
arduinoCopy codemy-nextjs-app/
├── app/
│ ├── dashboard/
│ │ ├── settings/
│ │ │ └── profile/
│ │ │ └── page.tsx
│ │ └── page.tsx
│ ├── layout.tsx
│ └── page.tsx
├── public/
├── next.config.js
└── ...
```
**实施:**
```tsx
tsxCopy code// app/dashboard/settings/profile/page.tsx
export default function ProfileSettingsPage() {
return (
Profile Settings
Manage your profile information here.
);
}
```
**解释:**
- **深层嵌套:** `dashboard/settings/profile/` 目录下的 `page.tsx` 文件对应于 `/dashboard/settings/profile` 路由。
- **层次反映:** 目录结构反映了 URL 路径,增强了可维护性和清晰度。
通配符路由
通配符路由处理多个嵌套段或未知路径,提供路由处理的灵活性。
**示例:`/*` 路由**
**文件结构:**
```arduino
my-nextjs-app/
├── app/
│ ├── [..slug]/
│ │ └── page.tsx
│ ├── layout.tsx
│ └── page.tsx
├── public/
├── next.config.js
└── ...
```
**实施:**
```tsx
// app/[...slug]/page.tsx
interface CatchAllProps {
params: { slug: string[] }
}
export default function CatchAllPage({ params }: CatchAllProps) {
const { slug } = params
const fullPath = `/${slug.join("/")}`
return (
Catch-All Route
You have navigated to: {fullPath}
)
}
```
**解释:**
- **Catch-All 段:** `[...slug]` 捕获所有剩余的路径段作为数组。
- **用法:** 适用于处理动态路由场景,如用户生成的路径、嵌套类别等。
- **路由匹配:** 像 `/anything/here`、`/foo/bar/baz` 等路径由此组件处理。
### 潜在的客户端漏洞
虽然 Next.js 提供了安全的基础,但不当的编码实践可能会引入漏洞。关键的客户端漏洞包括:
跨站脚本攻击 (XSS)
XSS 攻击发生在恶意脚本被注入到受信任的网站时。攻击者可以在用户的浏览器中执行脚本,窃取数据或代表用户执行操作。
**易受攻击代码示例:**
```jsx
// Dangerous: Injecting user input directly into HTML
function Comment({ userInput }) {
return
}
```
**为什么它容易受到攻击:** 使用 `dangerouslySetInnerHTML` 处理不受信任的输入允许攻击者注入恶意脚本。
客户端模板注入
当用户输入在模板中处理不当时发生,允许攻击者注入和执行模板或表达式。
**易受攻击代码示例:**
```jsx
import React from "react"
import ejs from "ejs"
function RenderTemplate({ template, data }) {
const html = ejs.render(template, data)
return
}
```
**为什么它容易受到攻击:** 如果 `template` 或 `data` 包含恶意内容,可能导致意外代码的执行。
客户端路径遍历
这是一种漏洞,允许攻击者操纵客户端路径以执行意外操作,例如跨站请求伪造(CSRF)。与针对服务器文件系统的服务器端路径遍历不同,CSPT 侧重于利用客户端机制将合法的 API 请求重定向到恶意端点。
**易受攻击代码示例:**
一个 Next.js 应用程序允许用户上传和下载文件。下载功能在客户端实现,用户可以指定要下载的文件路径。
```jsx
// 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 (
Download File
setFilePath(e.target.value)}
placeholder="Enter file path"
/>
)
}
```
#### 攻击场景
1. **攻击者的目标**:通过操纵 `filePath` 执行 CSRF 攻击以删除关键文件(例如,`admin/config.json`)。
2. **利用 CSPT**:
- **恶意输入**:攻击者构造一个带有操纵的 `filePath` 的 URL,例如 `../deleteFile/config.json`。
- **结果 API 调用**:客户端代码向 `/api/files/../deleteFile/config.json` 发出请求。
- **服务器的处理**:如果服务器不验证 `filePath`,则会处理该请求,可能删除或暴露敏感文件。
3. **执行 CSRF**:
- **构造的链接**:攻击者向受害者发送一个链接或嵌入一个恶意脚本,触发带有操纵的 `filePath` 的下载请求。
- **结果**:受害者在不知情的情况下执行该操作,导致未经授权的文件访问或删除。
#### 为什么它容易受到攻击
- **缺乏输入验证**:客户端允许任意 `filePath` 输入,从而启用路径遍历。
- **信任客户端输入**:服务器端 API 信任并处理未经过清理的 `filePath`。
- **潜在的 API 操作**:如果 API 端点执行状态更改操作(例如,删除、修改文件),则可能通过 CSPT 被利用。
## Next.js 中的服务器端
### 服务器端渲染 (SSR)
页面在每个请求时在服务器上渲染,确保用户接收到完全渲染的 HTML。在这种情况下,您应该创建自己的自定义服务器来处理请求。
**用例:**
- 经常变化的动态内容。
- SEO 优化,因为搜索引擎可以抓取完全渲染的页面。
**实现:**
```jsx
// 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 {data.title}
}
export default HomePage
```
### 静态网站生成 (SSG)
页面在构建时预渲染,从而实现更快的加载时间和减少服务器负载。
**用例:**
- 不经常更改的内容。
- 博客、文档、营销页面。
**实现:**
```jsx
// 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 {data.title}
}
export default HomePage
```
### Serverless Functions (API Routes)
Next.js 允许创建作为无服务器函数的 API 端点。这些函数按需运行,无需专用服务器。
**用例:**
- 处理表单提交。
- 与数据库交互。
- 处理数据或与第三方 API 集成。
**实现:**
随着 Next.js 13 中 `app` 目录的引入,路由和 API 处理变得更加灵活和强大。这种现代方法与基于文件的路由系统紧密对齐,但引入了增强的功能,包括对服务器和客户端组件的支持。
#### 基本路由处理程序
**文件结构:**
```go
my-nextjs-app/
├── app/
│ └── api/
│ └── hello/
│ └── route.js
├── package.json
└── ...
```
**实施:**
```javascript
// 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` 构造函数返回响应,从而更好地控制头部和状态码。
#### 如何处理其他路径和方法:
处理特定的 HTTP 方法
Next.js 13+ 允许您在同一个 `route.js` 或 `route.ts` 文件中定义特定 HTTP 方法的处理程序,从而促进更清晰和更有组织的代码。
**示例:**
```javascript
// 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` 提供对路由参数的访问。
- **增强响应:** 更好地控制响应对象,实现精确的头部和状态码管理。
通用和嵌套路由
Next.js 13+ 支持高级路由功能,如通用路由和嵌套 API 路由,允许更动态和可扩展的 API 结构。
**通用路由示例:**
```javascript
// 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" },
})
}
```
**解释:**
- **语法:** `[...]` 表示一个捕获所有嵌套路径的通用段。
- **用法:** 对于需要处理不同路由深度或动态段的 API 很有用。
**嵌套路由示例:**
```javascript
// 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 路由
**文件结构:**
```go
goCopy codemy-nextjs-app/
├── pages/
│ └── api/
│ └── hello.js
├── package.json
└── ...
```
**实施:**
```javascript
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 路由
**文件结构:**
```bash
bashCopy codemy-nextjs-app/
├── pages/
│ └── api/
│ └── users/
│ └── [id].js
├── package.json
└── ...
```
**实施:**
```javascript
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 方法(`GET`、`PUT`、`DELETE` 等)。
#### 处理不同的 HTTP 方法
虽然基本的 API 路由示例在一个函数中处理所有 HTTP 方法,但您可以将代码结构化,以明确处理每个方法,以提高清晰度和可维护性。
**示例:**
```javascript
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路由,以减轻跨源资源共享(CORS)漏洞。
**错误配置示例:**
```javascript
// 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`** 文件内:
```javascript
// 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发出请求,可能滥用数据检索、数据操作或代表经过身份验证的用户触发不必要的操作等功能。
{{#ref}}
../../pentesting-web/cors-bypass.md
{{#endref}}
### 客户端的服务器代码暴露
**服务器使用的代码也可以很容易地在客户端暴露和使用**,确保代码文件在客户端永远不被暴露的最佳方法是在文件开头使用此导入:
```js
import "server-only"
```
## 关键文件及其角色
### `middleware.ts` / `middleware.js`
**位置:** 项目的根目录或 `src/` 中。
**目的:** 在请求处理之前,在服务器端无服务器函数中执行代码,允许进行身份验证、重定向或修改响应等任务。
**执行流程:**
1. **传入请求:** 中间件拦截请求。
2. **处理:** 根据请求执行操作(例如,检查身份验证)。
3. **响应修改:** 可以更改响应或将控制权传递给下一个处理程序。
**示例用例:**
- 重定向未认证用户。
- 添加自定义头部。
- 记录请求。
**示例配置:**
```typescript
// 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*"],
}
```
### `next.config.js`
**位置:** 项目的根目录。
**目的:** 配置 Next.js 的行为,启用或禁用功能,自定义 webpack 配置,设置环境变量,并配置多个安全功能。
**关键安全配置:**
安全头部
安全头部通过指示浏览器如何处理内容来增强应用程序的安全性。它们有助于减轻各种攻击,如跨站脚本 (XSS)、点击劫持和 MIME 类型嗅探:
- 内容安全策略 (CSP)
- X-Frame-Options
- X-Content-Type-Options
- 严格传输安全 (HSTS)
- 引用者政策
**示例:**
```javascript
// 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 为性能优化图像,但错误配置可能导致安全漏洞,例如允许不受信任的来源注入恶意内容。
**错误配置示例:**
```javascript
// next.config.js
module.exports = {
images: {
domains: ["*"], // Allows images from any domain
},
}
```
**问题:**
- **`'*'`:** 允许从任何外部来源加载图像,包括不受信任或恶意的域。攻击者可以托管包含恶意负载或误导用户内容的图像。
- 另一个问题可能是允许一个**任何人都可以上传图像的域**(如 `raw.githubusercontent.com`)
**攻击者如何利用它:**
通过从恶意来源注入图像,攻击者可以执行网络钓鱼攻击,显示误导性信息,或利用图像渲染库中的漏洞。
环境变量暴露
安全地管理敏感信息,如 API 密钥和数据库凭据,而不将其暴露给客户端。
#### a. 暴露敏感变量
**错误配置示例:**
```javascript
// 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 重定向和重写,确保用户被适当地引导,而不会引入开放重定向漏洞。
#### a. 开放重定向漏洞
**错误配置示例:**
```javascript
// next.config.js
module.exports = {
async redirects() {
return [
{
source: "/redirect",
destination: (req) => req.query.url, // Dynamically redirects based on query parameter
permanent: false,
},
]
},
}
```
**问题:**
- **动态目标:** 允许用户指定任何 URL,从而启用开放重定向攻击。
- **信任用户输入:** 在没有验证的情况下重定向到用户提供的 URL 可能导致网络钓鱼、恶意软件传播或凭证盗窃。
**攻击者如何利用它:**
攻击者可以制作看似来自您域的 URL,但将用户重定向到恶意网站。例如:
```bash
https://yourdomain.com/redirect?url=https://malicious-site.com
```
用户信任原始域名可能会不知不觉地访问有害网站。
Webpack 配置
自定义 Next.js 应用程序的 Webpack 配置,如果处理不当,可能会无意中引入安全漏洞。
#### a. 暴露敏感模块
**错误配置示例:**
```javascript
// next.config.js
module.exports = {
webpack: (config, { isServer }) => {
if (!isServer) {
config.resolve.alias["@sensitive"] = path.join(__dirname, "secret-folder")
}
return config
},
}
```
**问题:**
- **暴露敏感路径:** 别名敏感目录并允许客户端访问可能会泄露机密信息。
- **捆绑秘密:** 如果敏感文件被捆绑到客户端,其内容可以通过源映射或检查客户端代码访问。
**攻击者如何利用它:**
攻击者可以访问或重建应用程序的目录结构,可能找到并利用敏感文件或数据。
### `pages/_app.js` 和 `pages/_document.js`
#### **`pages/_app.js`**
**目的:** 重写默认的 App 组件,允许全局状态、样式和布局组件。
**用例:**
- 注入全局 CSS。
- 添加布局包装器。
- 集成状态管理库。
**示例:**
```jsx
// pages/_app.js
import "../styles/globals.css"
function MyApp({ Component, pageProps }) {
return
}
export default MyApp
```
#### **`pages/_document.js`**
**目的:** 重写默认文档,允许自定义 HTML 和 Body 标签。
**使用案例:**
- 修改 `` 或 `` 标签。
- 添加元标签或自定义脚本。
- 集成第三方字体。
**示例:**
```jsx
// pages/_document.js
import Document, { Html, Head, Main, NextScript } from "next/document"
class MyDocument extends Document {
render() {
return (
{/* Custom fonts or meta tags */}
)
}
}
export default MyDocument
```
### 自定义服务器(可选)
**目的:** 虽然 Next.js 自带一个内置服务器,但您可以创建一个自定义服务器以满足高级用例,例如自定义路由或与现有后端服务集成。
**注意:** 使用自定义服务器可能会限制部署选项,特别是在像 Vercel 这样优化 Next.js 内置服务器的平台上。
**示例:**
```javascript
// 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` 访问环境变量。
- **绝不要在客户端暴露秘密:** 确保敏感变量仅在服务器端使用。
**示例:**
```javascript
// 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_` 前缀以供客户端使用。
### 身份验证和授权
**方法:**
- **基于会话的身份验证:** 使用 cookies 管理用户会话。
- **基于令牌的身份验证:** 实现无状态身份验证的 JWT。
- **第三方提供者:** 使用 `next-auth` 等库与 OAuth 提供者(例如,Google、GitHub)集成。
**安全实践:**
- **安全 Cookies:** 设置 `HttpOnly`、`Secure` 和 `SameSite` 属性。
- **密码哈希:** 在存储密码之前始终进行哈希处理。
- **输入验证:** 通过验证和清理输入来防止注入攻击。
**示例:**
```javascript
// 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 响应和静态资产实施缓存策略。
- **懒加载:** 仅在需要时加载组件或资产。
**示例:**
```jsx
// Dynamic Import with Code Splitting
import dynamic from "next/dynamic"
const HeavyComponent = dynamic(() => import("../components/HeavyComponent"), {
loading: () => Loading...
,
})
```
{{#include ../../banners/hacktricks-training.md}}