NextJS

Reading time: 23 minutes

tip

Ucz się i ćwicz AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Wsparcie HackTricks

Ogólna architektura aplikacji Next.js

Typowa struktura plików

Standardowy projekt Next.js przestrzega określonej struktury plików i katalogów, która ułatwia korzystanie z jego funkcji, takich jak routowanie, punkty końcowe API i zarządzanie statycznymi zasobami. Oto typowy układ:

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

Główne Katalogi i Pliki

  • public/: Hostuje statyczne zasoby, takie jak obrazy, czcionki i inne pliki. Pliki tutaj są dostępne pod ścieżką główną (/).
  • app/: Centralny katalog dla stron, układów, komponentów i tras API Twojej aplikacji. Wprowadza paradygmat App Router, umożliwiając zaawansowane funkcje routingu i segregację komponentów serwera i klienta.
  • app/layout.tsx: Definiuje główny układ dla Twojej aplikacji, otaczając wszystkie strony i zapewniając spójne elementy UI, takie jak nagłówki, stopki i paski nawigacyjne.
  • app/page.tsx: Służy jako punkt wejścia dla głównej trasy /, renderując stronę główną.
  • app/[route]/page.tsx: Obsługuje statyczne i dynamiczne trasy. Każdy folder w app/ reprezentuje segment trasy, a page.tsx w tych folderach odpowiada komponentowi trasy.
  • app/api/: Zawiera trasy API, umożliwiając tworzenie funkcji bezserwerowych, które obsługują żądania HTTP. Te trasy zastępują tradycyjny katalog pages/api.
  • app/components/: Zawiera wielokrotnego użytku komponenty React, które mogą być wykorzystywane w różnych stronach i układach.
  • app/styles/: Zawiera globalne pliki CSS i moduły CSS do stylizacji ograniczonej do komponentów.
  • app/utils/: Zawiera funkcje pomocnicze, moduły pomocnicze i inną logikę niezwiązaną z UI, która może być współdzielona w całej aplikacji.
  • .env.local: Przechowuje zmienne środowiskowe specyficzne dla lokalnego środowiska deweloperskiego. Te zmienne nie są zatwierdzane w systemie kontroli wersji.
  • next.config.js: Dostosowuje zachowanie Next.js, w tym konfiguracje webpack, zmienne środowiskowe i ustawienia bezpieczeństwa.
  • tsconfig.json: Konfiguruje ustawienia TypeScript dla projektu, umożliwiając sprawdzanie typów i inne funkcje TypeScript.
  • package.json: Zarządza zależnościami projektu, skryptami i metadanymi.
  • README.md: Dostarcza dokumentację i informacje o projekcie, w tym instrukcje dotyczące konfiguracji, wytyczne dotyczące użytkowania i inne istotne szczegóły.
  • yarn.lock / package-lock.json: Zamyka zależności projektu do konkretnych wersji, zapewniając spójne instalacje w różnych środowiskach.

Klient w Next.js

Routing oparty na plikach w katalogu app

Katalog app jest fundamentem routingu w najnowszych wersjach Next.js. Wykorzystuje system plików do definiowania tras, co sprawia, że zarządzanie trasami jest intuicyjne i skalowalne.

Obsługa ścieżki głównej /

Struktura plików:

arduino
my-nextjs-app/
├── app/
│   ├── layout.tsx
│   └── page.tsx
├── public/
├── next.config.js
└── ...

Kluczowe pliki:

  • app/page.tsx: Obsługuje żądania do ścieżki głównej /.
  • app/layout.tsx: Definiuje układ aplikacji, otaczając wszystkie strony.

Implementacja:

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

Wyjaśnienie:

  • Definicja trasy: Plik page.tsx bezpośrednio pod katalogiem app odpowiada trasie /.
  • Renderowanie: Ten komponent renderuje zawartość strony głównej.
  • Integracja układu: Komponent HomePage jest otoczony przez layout.tsx, który może zawierać nagłówki, stopki i inne wspólne elementy.
Obsługa innych statycznych ścieżek

Przykład: Trasa /about

Struktura plików:

arduino
arduinoCopy codemy-nextjs-app/
├── app/
│   ├── about/
│   │   └── page.tsx
│   ├── layout.tsx
│   └── page.tsx
├── public/
├── next.config.js
└── ...

Wdrożenie:

tsx
// app/about/page.tsx

export default function AboutPage() {
return (
<div>
<h1>About Us</h1>
<p>Learn more about our mission and values.</p>
</div>
)
}

Wyjaśnienie:

  • Definicja trasy: Plik page.tsx w folderze about odpowiada trasie /about.
  • Renderowanie: Ten komponent renderuje zawartość strony o nas.
Dynamiczne trasy

Dynamiczne trasy umożliwiają obsługę ścieżek z zmiennymi segmentami, co pozwala aplikacjom na wyświetlanie zawartości na podstawie parametrów, takich jak identyfikatory, slugi itp.

Przykład: Trasa /posts/[id]

Struktura plików:

arduino
arduinoCopy codemy-nextjs-app/
├── app/
│   ├── posts/
│   │   └── [id]/
│   │       └── page.tsx
│   ├── layout.tsx
│   └── page.tsx
├── public/
├── next.config.js
└── ...

Wdrożenie:

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 (
<div>
<h1>Post #{id}</h1>
<p>This is the content of post {id}.</p>
</div>
);
}

Wyjaśnienie:

  • Dynamiczny segment: [id] oznacza dynamiczny segment w trasie, przechwytując parametr id z URL.
  • Dostęp do parametrów: Obiekt params zawiera dynamiczne parametry, dostępne w komponencie.
  • Dopasowywanie tras: Każda ścieżka pasująca do /posts/*, taka jak /posts/1, /posts/abc itp., będzie obsługiwana przez ten komponent.
Zagnieżdżone trasy

Next.js obsługuje zagnieżdżone trasowanie, umożliwiając hierarchiczne struktury tras, które odzwierciedlają układ katalogów.

Przykład: /dashboard/settings/profile Trasa

Struktura plików:

arduino
arduinoCopy codemy-nextjs-app/
├── app/
│   ├── dashboard/
│   │   ├── settings/
│   │   │   └── profile/
│   │   │       └── page.tsx
│   │   └── page.tsx
│   ├── layout.tsx
│   └── page.tsx
├── public/
├── next.config.js
└── ...

Wdrożenie:

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

Wyjaśnienie:

  • Głębokie zagnieżdżenie: Plik page.tsx wewnątrz dashboard/settings/profile/ odpowiada trasie /dashboard/settings/profile.
  • Odbicie hierarchii: Struktura katalogów odzwierciedla ścieżkę URL, co zwiększa łatwość utrzymania i przejrzystość.
Trasy Catch-All

Trasy catch-all obsługują wiele zagnieżdżonych segmentów lub nieznane ścieżki, zapewniając elastyczność w obsłudze tras.

Przykład: trasa /*

Struktura plików:

arduino
my-nextjs-app/
├── app/
│   ├── [..slug]/
│   │   └── page.tsx
│   ├── layout.tsx
│   └── page.tsx
├── public/
├── next.config.js
└── ...

Wdrożenie:

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

Wyjaśnienie:

  • Segment Catch-All: [...slug] przechwytuje wszystkie pozostałe segmenty ścieżki jako tablicę.
  • Zastosowanie: Przydatne do obsługi dynamicznych scenariuszy routingu, takich jak ścieżki generowane przez użytkowników, zagnieżdżone kategorie itp.
  • Dopasowywanie tras: Ścieżki takie jak /anything/here, /foo/bar/baz itp. są obsługiwane przez ten komponent.

Potencjalne luki w zabezpieczeniach po stronie klienta

Chociaż Next.js zapewnia bezpieczną podstawę, niewłaściwe praktyki kodowania mogą wprowadzać luki. Kluczowe luki po stronie klienta obejmują:

Cross-Site Scripting (XSS)

Ataki XSS występują, gdy złośliwe skrypty są wstrzykiwane do zaufanych stron internetowych. Napastnicy mogą wykonywać skrypty w przeglądarkach użytkowników, kradnąc dane lub wykonując działania w imieniu użytkownika.

Przykład podatnego kodu:

jsx
// Dangerous: Injecting user input directly into HTML
function Comment({ userInput }) {
return <div dangerouslySetInnerHTML={{ __html: userInput }} />
}

Dlaczego jest podatne: Użycie dangerouslySetInnerHTML z nieufnym wejściem pozwala atakującym na wstrzykiwanie złośliwych skryptów.

Wstrzykiwanie szablonów po stronie klienta

Występuje, gdy dane wejściowe użytkownika są niewłaściwie obsługiwane w szablonach, co pozwala atakującym na wstrzykiwanie i wykonywanie szablonów lub wyrażeń.

Przykład podatnego kodu:

jsx
import React from "react"
import ejs from "ejs"

function RenderTemplate({ template, data }) {
const html = ejs.render(template, data)
return <div dangerouslySetInnerHTML={{ __html: html }} />
}

Dlaczego jest podatne: Jeśli template lub data zawiera złośliwą treść, może to prowadzić do wykonania niezamierzonego kodu.

Przechodzenie po ścieżkach po stronie klienta

To podatność, która pozwala atakującym manipulować ścieżkami po stronie klienta, aby wykonać niezamierzone działania, takie jak Cross-Site Request Forgery (CSRF). W przeciwieństwie do przechodzenia po ścieżkach po stronie serwera, które celuje w system plików serwera, CSPT koncentruje się na wykorzystywaniu mechanizmów po stronie klienta do przekierowywania legalnych żądań API do złośliwych punktów końcowych.

Przykład podatnego kodu:

Aplikacja Next.js pozwala użytkownikom na przesyłanie i pobieranie plików. Funkcja pobierania jest zaimplementowana po stronie klienta, gdzie użytkownicy mogą określić ścieżkę pliku do pobrania.

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

Scenariusz Ataku

  1. Cel Atakującego: Wykonanie ataku CSRF w celu usunięcia krytycznego pliku (np. admin/config.json) poprzez manipulację filePath.
  2. Wykorzystanie CSPT:
  • Złośliwy Wkład: Atakujący tworzy URL z manipulowanym filePath, takim jak ../deleteFile/config.json.
  • Wynikowe Wywołanie API: Kod po stronie klienta wysyła żądanie do /api/files/../deleteFile/config.json.
  • Obsługa przez Serwer: Jeśli serwer nie weryfikuje filePath, przetwarza żądanie, potencjalnie usuwając lub ujawniając wrażliwe pliki.
  1. Wykonanie CSRF:
  • Stworzony Link: Atakujący wysyła ofierze link lub osadza złośliwy skrypt, który wywołuje żądanie pobrania z manipulowanym filePath.
  • Wynik: Ofiara nieświadomie wykonuje akcję, co prowadzi do nieautoryzowanego dostępu do plików lub ich usunięcia.

Dlaczego Jest To Wrażliwe

  • Brak Weryfikacji Wkładu: Po stronie klienta dozwolone są dowolne wkłady filePath, co umożliwia przejście przez ścieżki.
  • Zaufanie do Wkładów Klienta: API po stronie serwera ufa i przetwarza filePath bez sanitizacji.
  • Potencjalne Akcje API: Jeśli punkt końcowy API wykonuje akcje zmieniające stan (np. usuwanie, modyfikowanie plików), może być wykorzystywany za pomocą CSPT.

Po Stronie Serwera w Next.js

Renderowanie Po Stronie Serwera (SSR)

Strony są renderowane na serwerze przy każdym żądaniu, zapewniając, że użytkownik otrzymuje w pełni renderowany HTML. W tym przypadku powinieneś stworzyć własny niestandardowy serwer do przetwarzania żądań.

Przykłady Zastosowania:

  • Dynamiczna treść, która często się zmienia.
  • Optymalizacja SEO, ponieważ wyszukiwarki mogą indeksować w pełni renderowaną stronę.

Implementacja:

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 <div>{data.title}</div>
}

export default HomePage

Static Site Generation (SSG)

Strony są wstępnie renderowane w czasie budowy, co skutkuje szybszym czasem ładowania i zmniejszonym obciążeniem serwera.

Use Cases:

  • Treści, które nie zmieniają się często.
  • Blogi, dokumentacja, strony marketingowe.

Implementation:

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 <div>{data.title}</div>
}

export default HomePage

Funkcje bezserwerowe (Trasy API)

Next.js umożliwia tworzenie punktów końcowych API jako funkcji bezserwerowych. Te funkcje działają na żądanie bez potrzeby posiadania dedykowanego serwera.

Przykłady użycia:

  • Obsługa przesyłania formularzy.
  • Interakcja z bazami danych.
  • Przetwarzanie danych lub integracja z zewnętrznymi API.

Implementacja:

Wraz z wprowadzeniem katalogu app w Next.js 13, routowanie i obsługa API stały się bardziej elastyczne i potężne. To nowoczesne podejście ściśle współpracuje z systemem routingu opartym na plikach, ale wprowadza ulepszone możliwości, w tym wsparcie dla komponentów serwerowych i klienckich.

Podstawowy obsługiwacz tras

Struktura plików:

go
my-nextjs-app/
├── app/
│   └── api/
│       └── hello/
│           └── route.js
├── package.json
└── ...

Wdrożenie:

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

Wyjaśnienie:

  • Lokalizacja: Trasy API znajdują się w katalogu app/api/.
  • Nazewnictwo plików: Każdy punkt końcowy API znajduje się w swoim własnym folderze zawierającym plik route.js lub route.ts.
  • Eksportowane funkcje: Zamiast pojedynczego domyślnego eksportu, eksportowane są specyficzne funkcje metod HTTP (np. GET, POST).
  • Obsługa odpowiedzi: Użyj konstruktora Response, aby zwracać odpowiedzi, co pozwala na większą kontrolę nad nagłówkami i kodami statusu.

Jak obsługiwać inne ścieżki i metody:

Obsługa specyficznych metod HTTP

Next.js 13+ pozwala na definiowanie handlerów dla specyficznych metod HTTP w tym samym pliku route.js lub route.ts, co promuje jaśniejszy i bardziej zorganizowany kod.

Przykład:

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" },
})
}

Wyjaśnienie:

  • Wielokrotne Eksporty: Każda metoda HTTP (GET, PUT, DELETE) ma swoją własną funkcję eksportowaną.
  • Parametry: Drugi argument zapewnia dostęp do parametrów trasy za pomocą params.
  • Ulepszone Odpowiedzi: Większa kontrola nad obiektami odpowiedzi, umożliwiająca precyzyjne zarządzanie nagłówkami i kodami statusu.
Trasy Catch-All i Zagnieżdżone

Next.js 13+ obsługuje zaawansowane funkcje routingu, takie jak trasy catch-all i zagnieżdżone trasy API, co pozwala na bardziej dynamiczne i skalowalne struktury API.

Przykład Trasy Catch-All:

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" },
})
}

Wyjaśnienie:

  • Składnia: [...] oznacza segment przechwytujący, który uchwyca wszystkie zagnieżdżone ścieżki.
  • Zastosowanie: Przydatne dla API, które muszą obsługiwać różne głębokości tras lub dynamiczne segmenty.

Przykład zagnieżdżonych tras:

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" },
}
)
}

Wyjaśnienie:

  • Głębokie zagnieżdżenie: Umożliwia hierarchiczne struktury API, odzwierciedlające relacje zasobów.
  • Dostęp do parametrów: Łatwy dostęp do wielu parametrów trasy za pomocą obiektu params.
Obsługa tras API w Next.js 12 i wcześniejszych wersjach

Trasy API w katalogu pages (Next.js 12 i wcześniejsze)

Zanim Next.js 13 wprowadził katalog app i ulepszone możliwości routingu, trasy API były głównie definiowane w katalogu pages. To podejście jest nadal szeroko stosowane i wspierane w Next.js 12 i wcześniejszych wersjach.

Podstawowa trasa API

Struktura plików:

go
goCopy codemy-nextjs-app/
├── pages/
│   └── api/
│       └── hello.js
├── package.json
└── ...

Wdrożenie:

javascript
javascriptCopy code// pages/api/hello.js

export default function handler(req, res) {
res.status(200).json({ message: 'Hello, World!' });
}

Wyjaśnienie:

  • Lokalizacja: Trasy API znajdują się w katalogu pages/api/.
  • Eksport: Użyj export default, aby zdefiniować funkcję obsługi.
  • Podpis funkcji: Funkcja obsługi otrzymuje obiekty req (żądanie HTTP) i res (odpowiedź HTTP).
  • Routing: Nazwa pliku (hello.js) odpowiada punktowi końcowemu /api/hello.

Dynamiczne trasy API

Struktura plików:

bash
bashCopy codemy-nextjs-app/
├── pages/
│   └── api/
│       └── users/
│           └── [id].js
├── package.json
└── ...

Wdrożenie:

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

Wyjaśnienie:

  • Dynamiczne segmenty: Kwadratowe nawiasy ([id].js) oznaczają dynamiczne segmenty trasy.
  • Dostęp do parametrów: Użyj req.query.id, aby uzyskać dostęp do dynamicznego parametru.
  • Obsługa metod: Wykorzystaj logikę warunkową do obsługi różnych metod HTTP (GET, PUT, DELETE itd.).

Obsługa różnych metod HTTP

Podczas gdy podstawowy przykład trasy API obsługuje wszystkie metody HTTP w jednej funkcji, możesz zorganizować swój kod, aby obsługiwał każdą metodę wyraźnie dla lepszej przejrzystości i łatwości utrzymania.

Przykład:

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

Najlepsze praktyki:

  • Separacja obowiązków: Wyraźnie oddziel logikę dla różnych metod HTTP.
  • Spójność odpowiedzi: Zapewnij spójne struktury odpowiedzi dla łatwiejszego przetwarzania po stronie klienta.
  • Obsługa błędów: Elegancko obsługuj nieobsługiwane metody i nieoczekiwane błędy.

Konfiguracja CORS

Kontroluj, które źródła mogą uzyskiwać dostęp do twoich tras API, łagodząc podatności na Cross-Origin Resource Sharing (CORS).

Zły przykład konfiguracji:

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",
},
})
}

Zauważ, że CORS można również skonfigurować we wszystkich trasach API w pliku 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
}

Problem:

  • Access-Control-Allow-Origin: '*': Zezwala każdej stronie na dostęp do API, co potencjalnie umożliwia złośliwym stronom interakcję z Twoim API bez ograniczeń.
  • Szerokie zezwolenie na metody: Zezwolenie na wszystkie metody może umożliwić atakującym wykonywanie niepożądanych działań.

Jak atakujący to wykorzystują:

Atakujący mogą tworzyć złośliwe strony, które wysyłają żądania do Twojego API, potencjalnie nadużywając funkcji takich jak pobieranie danych, manipulacja danymi lub wywoływanie niepożądanych działań w imieniu uwierzytelnionych użytkowników.

{{#ref}} ../../pentesting-web/cors-bypass.md {{#endref}}

Ekspozycja kodu serwera po stronie klienta

Łatwo jest używać kodu używanego przez serwer również w kodzie eksponowanym i używanym przez stronę klienta, najlepszym sposobem na zapewnienie, że plik kodu nigdy nie jest eksponowany po stronie klienta, jest użycie tego importu na początku pliku:

js
import "server-only"

Kluczowe pliki i ich role

middleware.ts / middleware.js

Lokalizacja: W katalogu głównym projektu lub w src/.

Cel: Wykonuje kod w funkcji serverless po stronie serwera przed przetworzeniem żądania, umożliwiając takie zadania jak uwierzytelnianie, przekierowania lub modyfikowanie odpowiedzi.

Przebieg wykonania:

  1. Przychodzące żądanie: Middleware przechwytuje żądanie.
  2. Przetwarzanie: Wykonuje operacje na podstawie żądania (np. sprawdzenie uwierzytelnienia).
  3. Modyfikacja odpowiedzi: Może zmieniać odpowiedź lub przekazać kontrolę do następnego handlera.

Przykłady zastosowań:

  • Przekierowywanie nieautoryzowanych użytkowników.
  • Dodawanie niestandardowych nagłówków.
  • Rejestrowanie żądań.

Przykładowa konfiguracja:

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

Lokalizacja: Korzeń projektu.

Cel: Konfiguruje zachowanie Next.js, włączając lub wyłączając funkcje, dostosowując konfiguracje webpack, ustawiając zmienne środowiskowe i konfigurowanie kilku funkcji zabezpieczeń.

Kluczowe konfiguracje zabezpieczeń:

Nagłówki zabezpieczeń

Nagłówki zabezpieczeń zwiększają bezpieczeństwo Twojej aplikacji, instruując przeglądarki, jak obsługiwać treści. Pomagają w łagodzeniu różnych ataków, takich jak Cross-Site Scripting (XSS), Clickjacking i sniffing typu MIME:

  • Content Security Policy (CSP)
  • X-Frame-Options
  • X-Content-Type-Options
  • Strict-Transport-Security (HSTS)
  • Referrer Policy

Przykłady:

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...
],
},
]
},
}
Ustawienia optymalizacji obrazów

Next.js optymalizuje obrazy pod kątem wydajności, ale błędne konfiguracje mogą prowadzić do luk w zabezpieczeniach, takich jak umożliwienie nieufnym źródłom wstrzykiwania złośliwej treści.

Zły przykład konfiguracji:

javascript
// next.config.js

module.exports = {
images: {
domains: ["*"], // Allows images from any domain
},
}

Problem:

  • '*': Zezwala na ładowanie obrazów z dowolnego zewnętrznego źródła, w tym z nieufnych lub złośliwych domen. Napastnicy mogą hostować obrazy zawierające złośliwe ładunki lub treści, które wprowadzają użytkowników w błąd.
  • Innym problemem może być zezwolenie na domenę gdzie ktokolwiek może przesłać obraz (jak raw.githubusercontent.com)

Jak napastnicy to wykorzystują:

Poprzez wstrzykiwanie obrazów z złośliwych źródeł, napastnicy mogą przeprowadzać ataki phishingowe, wyświetlać wprowadzające w błąd informacje lub wykorzystywać luki w bibliotekach renderujących obrazy.

Ekspozycja Zmiennych Środowiskowych

Zarządzaj wrażliwymi informacjami, takimi jak klucze API i dane uwierzytelniające do bazy danych, w sposób bezpieczny, nie ujawniając ich klientowi.

a. Ekspozycja Wrażliwych Zmiennych

Zły Przykład Konfiguracji:

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

Problem:

  • SECRET_API_KEY: Bez prefiksu NEXT_PUBLIC_, Next.js nie udostępnia zmiennych klientowi. Jednak, jeśli przez pomyłkę zostanie dodany prefiks (np. NEXT_PUBLIC_SECRET_API_KEY), staje się dostępny po stronie klienta.

How attackers abuse it:

Jeśli wrażliwe zmienne są udostępnione klientowi, atakujący mogą je odzyskać, przeglądając kod po stronie klienta lub żądania sieciowe, uzyskując nieautoryzowany dostęp do API, baz danych lub innych usług.

Redirects

Zarządzaj przekierowaniami URL i przepisywaniem w swojej aplikacji, zapewniając, że użytkownicy są odpowiednio kierowani, nie wprowadzając podatności na otwarte przekierowania.

a. Open Redirect Vulnerability

Bad Configuration Example:

javascript
// next.config.js

module.exports = {
async redirects() {
return [
{
source: "/redirect",
destination: (req) => req.query.url, // Dynamically redirects based on query parameter
permanent: false,
},
]
},
}

Problem:

  • Dynamic Destination: Umożliwia użytkownikom określenie dowolnego URL, co pozwala na ataki typu open redirect.
  • Trusting User Input: Przekierowania do URL podanych przez użytkowników bez walidacji mogą prowadzić do phishingu, dystrybucji złośliwego oprogramowania lub kradzieży poświadczeń.

How attackers abuse it:

Atakujący mogą tworzyć URL, które wydają się pochodzić z twojej domeny, ale przekierowują użytkowników na złośliwe strony. Na przykład:

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

Użytkownicy ufający oryginalnej domenie mogą nieświadomie przechodzić do szkodliwych stron internetowych.

Konfiguracja Webpack

Dostosuj konfiguracje Webpack dla swojej aplikacji Next.js, które mogą nieumyślnie wprowadzać luki w zabezpieczeniach, jeśli nie są obsługiwane ostrożnie.

a. Ujawnianie wrażliwych modułów

Zły przykład konfiguracji:

javascript
// next.config.js

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

Problem:

  • Eksponowanie wrażliwych ścieżek: Aliasowanie wrażliwych katalogów i umożliwienie dostępu po stronie klienta może prowadzić do wycieku poufnych informacji.
  • Pakowanie sekretów: Jeśli wrażliwe pliki są pakowane dla klienta, ich zawartość staje się dostępna poprzez mapy źródłowe lub inspekcję kodu po stronie klienta.

Jak atakujący to wykorzystują:

Atakujący mogą uzyskać dostęp do struktury katalogów aplikacji lub ją odtworzyć, potencjalnie znajdując i wykorzystując wrażliwe pliki lub dane.

pages/_app.js i pages/_document.js

pages/_app.js

Cel: Nadpisuje domyślny komponent App, umożliwiając globalny stan, style i komponenty układu.

Przykłady użycia:

  • Wstrzykiwanie globalnego CSS.
  • Dodawanie opakowań układu.
  • Integracja bibliotek zarządzania stanem.

Przykład:

jsx
// pages/_app.js
import "../styles/globals.css"

function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}

export default MyApp

pages/_document.js

Cel: Nadpisuje domyślny dokument, umożliwiając dostosowanie tagów HTML i Body.

Przykłady użycia:

  • Modyfikacja tagów <html> lub <body>.
  • Dodawanie tagów meta lub niestandardowych skryptów.
  • Integracja czcionek zewnętrznych.

Przykład:

jsx
// 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 (Opcjonalnie)

Cel: Chociaż Next.js ma wbudowany serwer, możesz stworzyć własny serwer do zaawansowanych przypadków użycia, takich jak niestandardowe routowanie lub integracja z istniejącymi usługami backendowymi.

Uwaga: Użycie niestandardowego serwera może ograniczyć opcje wdrożenia, szczególnie na platformach takich jak Vercel, które optymalizują wbudowany serwer Next.js.

Przykład:

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

Dodatkowe rozważania architektoniczne i bezpieczeństwa

Zmienne środowiskowe i konfiguracja

Cel: Zarządzanie wrażliwymi informacjami i ustawieniami konfiguracyjnymi poza kodem źródłowym.

Najlepsze praktyki:

  • Używaj plików .env: Przechowuj zmienne, takie jak klucze API, w .env.local (wyłączone z kontroli wersji).
  • Bezpieczny dostęp do zmiennych: Używaj process.env.VARIABLE_NAME, aby uzyskać dostęp do zmiennych środowiskowych.
  • Nigdy nie ujawniaj sekretów po stronie klienta: Upewnij się, że wrażliwe zmienne są używane tylko po stronie serwera.

Przykład:

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

Uwaga: Aby ograniczyć zmienne tylko do serwera, pomiń je w obiekcie env lub poprzedź je prefiksem NEXT_PUBLIC_ dla ekspozycji po stronie klienta.

Uwierzytelnianie i autoryzacja

Podejście:

  • Uwierzytelnianie oparte na sesji: Użyj ciasteczek do zarządzania sesjami użytkowników.
  • Uwierzytelnianie oparte na tokenach: Wdrażaj JWT do stateless authentication.
  • Dostawcy zewnętrzni: Integruj się z dostawcami OAuth (np. Google, GitHub) za pomocą bibliotek takich jak next-auth.

Praktyki bezpieczeństwa:

  • Bezpieczne ciasteczka: Ustaw atrybuty HttpOnly, Secure i SameSite.
  • Hashowanie haseł: Zawsze haszuj hasła przed ich przechowywaniem.
  • Walidacja danych wejściowych: Zapobiegaj atakom typu injection poprzez walidację i sanitizację danych wejściowych.

Przykład:

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

Optymalizacja Wydajności

Strategie:

  • Optymalizacja Obrazów: Użyj komponentu next/image w Next.js do automatycznej optymalizacji obrazów.
  • Podział Kodu: Wykorzystaj dynamiczne importy do podziału kodu i zmniejszenia czasów ładowania początkowego.
  • Cache: Wdroż strategie cache dla odpowiedzi API i statycznych zasobów.
  • Ładowanie na Żądanie: Ładuj komponenty lub zasoby tylko wtedy, gdy są potrzebne.

Przykład:

jsx
// Dynamic Import with Code Splitting
import dynamic from "next/dynamic"

const HeavyComponent = dynamic(() => import("../components/HeavyComponent"), {
loading: () => <p>Loading...</p>,
})

tip

Ucz się i ćwicz AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Wsparcie HackTricks