1187 lines
36 KiB
Markdown

# NextJS
{{#include ../../banners/hacktricks-training.md}}
## 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.
<details>
<summary>Obsługa ścieżki głównej /</summary>
**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.
</details>
<details>
<summary>Obsługa innych statycznych ścieżek</summary>
**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.
</details>
<details>
<summary>Dynamiczne trasy</summary>
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.
</details>
<details>
<summary>Zagnieżdżone trasy</summary>
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ść.
</details>
<details>
<summary>Trasy Catch-All</summary>
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.
</details>
### 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ą:
<details>
<summary>Cross-Site Scripting (XSS)</summary>
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.
</details>
<details>
<summary>Wstrzykiwanie szablonów po stronie klienta</summary>
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.
</details>
<details>
<summary>Przechodzenie po ścieżkach po stronie klienta</summary>
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.
3. **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.
</details>
## 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:
<details>
<summary>Obsługa specyficznych metod HTTP</summary>
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.
</details>
<details>
<summary>Trasy Catch-All i Zagnieżdżone</summary>
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`.
</details>
<details>
<summary>Obsługa tras API w Next.js 12 i wcześniejszych wersjach</summary>
## 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.
</details>
### 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ń:**
<details>
<summary>Nagłówki zabezpieczeń</summary>
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...
],
},
]
},
}
```
</details>
<details>
<summary>Ustawienia optymalizacji obrazów</summary>
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.
</details>
<details>
<summary>Ekspozycja Zmiennych Środowiskowych</summary>
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.
</details>
<details>
<summary>Redirects</summary>
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.
</details>
<details>
<summary>Konfiguracja Webpack</summary>
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.
</details>
### `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>,
})
```
{{#include ../../banners/hacktricks-training.md}}