NextJS

Tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Вивчайте та практикуйте Azure Hacking: 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

Core Directories and Files

  • public/: Містить статичні ресурси, такі як images, fonts та інші файли. Файли тут доступні за кореневим шляхом (/).
  • app/: Центральний каталог для сторінок, layout-ів, компонентів і API routes вашого application. Використовує парадигму App Router, що дозволяє просунуті можливості маршрутизації та розділення компонентів між server та client.
  • app/layout.tsx: Визначає root layout для вашого application, обгортаючи всі сторінки і забезпечуючи консистентні UI-елементи, такі як headers, footers та navigation bars.
  • app/page.tsx: Служить точкою входу для кореневого маршруту /, відображаючи home page.
  • app/[route]/page.tsx: Обробляє статичні та динамічні маршрути. Кожна папка всередині app/ представляє сегмент маршруту, а page.tsx у цих папках відповідає за компонент маршруту.
  • app/api/: Містить API routes, що дозволяє створювати serverless functions які обробляють HTTP-запити. Ці маршрути замінюють традиційну директорію pages/api.
  • app/components/: Містить повторно використовувані React components, які можна використовувати на різних сторінках і в layout-ах.
  • app/styles/: Містить глобальні CSS файли та CSS Modules для стилізації, обмеженої компонентом.
  • app/utils/: Містить utility functions, допоміжні модулі та іншу не-UI логіку, яку можна розділяти по додатку.
  • .env.local: Зберігає environment variables специфічні для локального development середовища. Ці змінні не піддаються коміту в систему контролю версій.
  • next.config.js: Налаштовує поведінку Next.js, включаючи webpack configurations, environment variables та security settings.
  • tsconfig.json: Налаштовує TypeScript параметри проєкту, забезпечуючи type checking та інші TypeScript можливості.
  • package.json: Керує залежностями проєкту, скриптами та метаданими.
  • README.md: Надає документацію та інформацію про проєкт, включаючи інструкції для налаштування, керівництва по використанню та інші релевантні деталі.
  • yarn.lock / package-lock.json: Фіксують залежності проєкту до конкретних версій, забезпечуючи узгоджені інсталяції в різних середовищах.

Client-Side in Next.js

File-Based Routing in the app Directory

Директорія app є наріжним каменем маршрутизації в останніх версіях Next.js. Вона використовує файлову систему для визначення маршрутів, роблячи керування маршрутами інтуїтивним і масштабованим.

Handling the Root Path /

File Structure:

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.
Динамічні маршрути

Динамічні маршрути дозволяють обробляти шляхи зі змінними сегментами, даючи можливість додаткам відображати вміст на основі параметрів, таких як IDs, slugs тощо.

Приклад: маршрут /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] позначає динамічний сегмент у маршруті, який захоплює параметр id з URL.
  • Доступ до параметрів: Об’єкт 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>
);
}

Пояснення:

  • Глибоке вкладення: The page.tsx file inside dashboard/settings/profile/ corresponds to the /dashboard/settings/profile route.
  • Відображення ієрархії: The directory structure reflects the URL path, enhancing maintainability and clarity.
Універсальні маршрути

Універсальні маршрути обробляють кілька вкладених сегментів або невідомі шляхи, забезпечуючи гнучкість у маршрутизації.

Приклад: маршрут /*

Структура файлів:

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-запитів на шкідливі кінцеві точки.

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: Perform a CSRF attack to delete a critical file (e.g., admin/config.json) by manipulating the filePath.
  2. Exploiting CSPT:
  • Malicious Input: The attacker crafts a URL with a manipulated filePath such as ../deleteFile/config.json.
  • Resulting API Call: The client-side code makes a request to /api/files/../deleteFile/config.json.
  • Server’s Handling: If the server does not validate the filePath, it processes the request, potentially deleting or exposing sensitive files.
  1. Executing CSRF:
  • Crafted Link: The attacker sends the victim a link or embeds a malicious script that triggers the download request with the manipulated filePath.
  • Outcome: The victim unknowingly executes the action, leading to unauthorized file access or deletion.

Why It’s Vulnerable

  • Lack of Input Validation: The client-side allows arbitrary filePath inputs, enabling path traversal.
  • Trusting Client Inputs: The server-side API trusts and processes the filePath without sanitization.
  • Potential API Actions: If the API endpoint performs state-changing actions (e.g., delete, modify files), it can be exploited via CSPT.

Серверна частина в 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

Генерація статичних сайтів (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 стали гнучкішими та потужнішими. Цей сучасний підхід тісно відповідає системі маршрутизації на базі файлів, але додає розширені можливості, включно з підтримкою серверних та клієнтських компонентів.

Базовий обробник маршруту

Структура файлів:

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 routes are placed under the app/api/ directory.
  • Іменування файлів: Кожен API-ендпоінт розміщено у власній папці, що містить файл route.js або route.ts.
  • Експортовані функції: Замість одного default-експорту експортуються функції для конкретних HTTP-методів (наприклад, GET, POST).
  • Обробка відповіді: Використовуйте конструктор Response для повернення відповіді, що дає більший контроль над заголовками та статусними кодами.

Як обробляти інші шляхи та методи:

Обробка конкретних HTTP-методів

Next.js 13+ дозволяє визначати обробники для конкретних HTTP-методів у тому самому файлі route.js або route.ts, що робить код більш зрозумілим та організованим.

Приклад:

// 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.
  • Розширені відповіді: Більший контроль над об’єктами відповіді, що дозволяє точно керувати заголовками та кодами стану.
Маршрути 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.
Обробка API-маршрутів у Next.js 12 і раніших версіях

API-маршрути в директорії pages (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-методів (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

Контролюйте, які origin можуть отримувати доступ до ваших API routes, щоб зменшити вразливості 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

Location: Корінь проекту або в межах src/.

Purpose: Виконує код у безсерверній функції на стороні сервера перед обробкою запиту, що дозволяє виконувати завдання, як-от автентифікація, редиректи або модифікація відповідей.

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

next.config.js

Розташування: Корінь проєкту.

Призначення: Налаштовує поведінку Next.js, вмикає або вимикає функції, кастомізує конфігурації webpack, задає змінні середовища та конфігурує кілька параметрів безпеки.

Ключові налаштування безпеки:

Заголовки безпеки

Заголовки безпеки підвищують захист вашого застосунку, вказуючи браузерам, як обробляти контент. Вони допомагають зменшити ризик таких атак, як 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
},
}

Проблема:

  • '*': Дозволяє завантажувати зображення з будь-яких зовнішніх джерел, включно з неперевіреними або шкідливими доменами. Зловмисники можуть розміщувати зображення, що містять malicious payloads або контент, який вводить користувачів в оману.
  • Іншою проблемою може бути дозволити домен де будь-хто може завантажувати зображення (наприклад raw.githubusercontent.com)

Як це використовують зловмисники:

Впроваджуючи зображення зі шкідливих джерел, зловмисники можуть проводити фішингові атаки, показувати оманливу інформацію або експлуатувати вразливості в бібліотеках рендерингу зображень.

Витік змінних середовища

Керуйте чутливою інформацією, такою як API-ключі та облікові дані баз даних, безпечно, не передаючи її на клієнт.

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.
  • Надмірна довіра до введення користувача: Перенаправлення на URL, надані користувачами без перевірки, може призвести до phishing, malware distribution або credential theft.

Як зловмисники це використовують:

Зловмисники можуть створювати URL, які виглядають так, ніби походять з вашого домену, але перенаправляють користувачів на шкідливі сайти. Наприклад:

https://yourdomain.com/redirect?url=https://malicious-site.com

Користувачі, які довіряють оригінальному домену, можуть ненавмисно перейти на шкідливі вебсайти.

Конфігурація Webpack

Налаштування конфігурацій Webpack для вашого додатка Next.js може ненавмисно призвести до появи вразливостей безпеки, якщо робити це необережно.

a. Розкриття конфіденційних модулів

Приклад поганої конфігурації:

// next.config.js

module.exports = {
webpack: (config, { isServer }) => {
if (!isServer) {
config.resolve.alias["@sensitive"] = path.join(__dirname, "secret-folder")
}
return config
},
}

Проблема:

  • Виявлення чутливих шляхів: Створення псевдонімів для чутливих директорій і надання доступу з client-side може призвести до leak конфіденційної інформації.
  • Пакування секретів: Якщо чутливі файли bundled для client-side, їх вміст стає доступним через source maps або інспектування client-side коду.

Як зловмисники це використовують:

Зловмисники можуть отримати доступ до або відтворити структуру директорій застосунку, потенційно знаходячи та експлуатуючи чутливі файли або дані.

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

Користувацький сервер (необов’язково)

Призначення: Хоча 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_ для доступу з клієнта.

Аутентифікація та авторизація

Підхід:

  • Аутентифікація на основі сесій: Використовуйте cookies для керування сесіями користувачів.
  • Аутентифікація на основі токенів: Реалізуйте JWTs для безстанової аутентифікації.
  • Постачальники третьої сторони: Інтегруйтеся з OAuth провайдерами (наприклад, Google, GitHub) за допомогою бібліотек, таких як next-auth.

Практики безпеки:

  • Безпечні 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 для автоматичної оптимізації зображень.
  • Розділення коду: Використовуйте динамічні імпорти для розділення коду та зменшення часу початкового завантаження.
  • Кешування: Реалізуйте стратегії кешування для відповідей 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 потрапляють на один і той же endpoint і розрізняються за допомогою build-specific hash, що відправляється в заголовку Next-Action. Приклад:

POST /
Next-Action: a9f8e2b4c7d1...

Коли productionBrowserSourceMaps увімкнено, minified JS chunks містять виклики createServerReference(...), які leak достатньо структури (плюс пов’язані source maps), щоб відновити відображення між action hash та оригінальною назвою функції. Це дозволяє транслювати хеші, помічені в Next-Action, у конкретні цілі, такі як deleteUserAccount() або exportFinancialData().

Підхід витягнення (regex on minified JS + optional 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*\)
  • Group 1: хеш серверної дії (40+ hex символів)
  • Group 2: символ або шлях, які можна відновити до оригінальної функції через source map, коли він присутній

If the script advertises a source map (trailer comment //# sourceMappingURL=<...>.map), fetch it and resolve the symbol/path to the original function name.

Практичний робочий процес

  • Пасивне виявлення під час перегляду: перехоплюйте запити з Next-Action headers та URL-адреси JS chunk.
  • Fetch the referenced JS bundles and accompanying *.map files (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).

Виклик прихованих дій (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) автоматизує вищезазначене в Burp:
  • Шукає в історії proxy JS chunks, витягує createServerReference(...) записи і парсить source maps, коли вони доступні.
  • Підтримує пошуковий hash↔function-name словник і усуває дублікати між збірками за іменем функції.
  • Може знайти валідний шаблон POST і відкрити готову до відправки вкладку Repeater з підставленим hash цільової дії.
  • Repo: https://github.com/Adversis/NextjsServerActionAnalyzer

Notes and limitations

  • Потребує увімкненого productionBrowserSourceMaps у production, щоб відновити імена з bundles/source maps.
  • Function-name disclosure не є вразливістю сам по собі; використовуйте його для наведення пошуку і перевіряйте авторизацію кожної дії.

References

Tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Вивчайте та практикуйте Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Підтримайте HackTricks