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
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegram lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów github.
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:
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, apage.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:
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:
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 katalogiemapp
odpowiada trasie/
. - Renderowanie: Ten komponent renderuje zawartość strony głównej.
- Integracja układu: Komponent
HomePage
jest otoczony przezlayout.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:
arduinoCopy codemy-nextjs-app/
├── app/
│ ├── about/
│ │ └── page.tsx
│ ├── layout.tsx
│ └── page.tsx
├── public/
├── next.config.js
└── ...
Wdrożenie:
// 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 folderzeabout
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:
arduinoCopy codemy-nextjs-app/
├── app/
│ ├── posts/
│ │ └── [id]/
│ │ └── page.tsx
│ ├── layout.tsx
│ └── page.tsx
├── public/
├── next.config.js
└── ...
Wdrożenie:
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 parametrid
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:
arduinoCopy codemy-nextjs-app/
├── app/
│ ├── dashboard/
│ │ ├── settings/
│ │ │ └── profile/
│ │ │ └── page.tsx
│ │ └── page.tsx
│ ├── layout.tsx
│ └── page.tsx
├── public/
├── next.config.js
└── ...
Wdrożenie:
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ątrzdashboard/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:
my-nextjs-app/
├── app/
│ ├── [..slug]/
│ │ └── page.tsx
│ ├── layout.tsx
│ └── page.tsx
├── public/
├── next.config.js
└── ...
Wdrożenie:
// 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:
// 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:
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.
// 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
- Cel Atakującego: Wykonanie ataku CSRF w celu usunięcia krytycznego pliku (np.
admin/config.json
) poprzez manipulacjęfilePath
. - 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.
- 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:
// 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:
// 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:
my-nextjs-app/
├── app/
│ └── api/
│ └── hello/
│ └── route.js
├── package.json
└── ...
Wdrożenie:
// 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
lubroute.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:
// 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:
// 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:
// 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:
goCopy codemy-nextjs-app/
├── pages/
│ └── api/
│ └── hello.js
├── package.json
└── ...
Wdrożenie:
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) ires
(odpowiedź HTTP). - Routing: Nazwa pliku (
hello.js
) odpowiada punktowi końcowemu/api/hello
.
Dynamiczne trasy API
Struktura plików:
bashCopy codemy-nextjs-app/
├── pages/
│ └── api/
│ └── users/
│ └── [id].js
├── package.json
└── ...
Wdrożenie:
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:
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:
// 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
:
// 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:
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:
- Przychodzące żądanie: Middleware przechwytuje żądanie.
- Przetwarzanie: Wykonuje operacje na podstawie żądania (np. sprawdzenie uwierzytelnienia).
- 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:
// 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:
// 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:
// 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:
// 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 prefiksuNEXT_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:
// 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:
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:
// 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:
// 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:
// 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:
// 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:
// 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
iSameSite
. - 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:
// 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:
// 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
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegram lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów github.