NextJS

Tip

Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Lernen & üben Sie Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Unterstützen Sie HackTricks

Allgemeine Architektur einer Next.js-Anwendung

Typische Dateistruktur

Ein Standard-Next.js-Projekt folgt einer bestimmten Datei- und Verzeichnisstruktur, die Funktionen wie Routing, API-Endpunkte und die Verwaltung statischer Assets erleichtert. Hier ist ein typisches Layout:

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

Kernverzeichnisse und Dateien

  • public/: Beherbergt statische Assets wie Bilder, Schriftarten und andere Dateien. Dateien hier sind unter dem Root-Pfad (/) erreichbar.
  • app/: Zentrales Verzeichnis für die Seiten, Layouts, Komponenten und API-Routen deiner Anwendung. Nutzt das App Router-Paradigma und ermöglicht erweiterte Routing-Funktionen sowie die Trennung von Server- und Client-Komponenten.
  • app/layout.tsx: Definiert das Root-Layout deiner Anwendung, umschließt alle Seiten und stellt konsistente UI-Elemente wie Header, Footer und Navigationsleisten bereit.
  • app/page.tsx: Dient als Einstiegspunkt für die Root-Route / und rendert die Startseite.
  • app/[route]/page.tsx: Verarbeitet statische und dynamische Routen. Jeder Ordner innerhalb von app/ repräsentiert ein Routensegment, und die page.tsx in diesen Ordnern entspricht der Komponente der Route.
  • app/api/: Enthält API-Routen, die es ermöglichen, Serverless-Funktionen zu erstellen, die HTTP-Anfragen verarbeiten. Diese Routen ersetzen das traditionelle pages/api-Verzeichnis.
  • app/components/: Beinhaltet wiederverwendbare React-Komponenten, die auf verschiedenen Seiten und Layouts genutzt werden können.
  • app/styles/: Enthält globale CSS-Dateien und CSS-Module für komponentenbezogene Styles.
  • app/utils/: Beinhaltet Utility-Funktionen, Hilfs-Module und andere nicht-UI-Logik, die in der Anwendung geteilt werden kann.
  • .env.local: Speichert Umgebungsvariablen, die spezifisch für die lokale Entwicklungsumgebung sind. Diese Variablen werden nicht in die Versionskontrolle eingecheckt.
  • next.config.js: Passt das Verhalten von Next.js an, einschließlich webpack-Konfigurationen, Umgebungsvariablen und Sicherheitseinstellungen.
  • tsconfig.json: Konfiguriert TypeScript-Einstellungen für das Projekt, aktiviert Typprüfung und andere TypeScript-Funktionen.
  • package.json: Verwaltet Projektabhängigkeiten, Skripte und Metadaten.
  • README.md: Bietet Dokumentation und Informationen zum Projekt, einschließlich Setup-Anweisungen, Nutzungshinweisen und anderen relevanten Details.
  • yarn.lock / package-lock.json: Bindet die Abhängigkeiten des Projekts an bestimmte Versionen und sorgt so für konsistente Installationen in verschiedenen Umgebungen.

Client-Seite in Next.js

Dateibasiertes Routing im app-Verzeichnis

Das app-Verzeichnis ist das Herzstück des Routings in den neuesten Next.js-Versionen. Es nutzt das Dateisystem zur Definition von Routen, was das Management von Routen intuitiv und skalierbar macht.

Umgang mit dem Root-Pfad /

Dateistruktur:

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

Wichtige Dateien:

  • app/page.tsx: Verarbeitet Anfragen an den Root-Pfad /.
  • app/layout.tsx: Definiert das Layout der Anwendung, das alle Seiten umschließt.

Implementierung:

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

Erklärung:

  • Route Definition: Die Datei page.tsx direkt unter dem app-Verzeichnis entspricht der Route /.
  • Rendering: Diese Komponente rendert den Inhalt für die Startseite.
  • Layout Integration: Die Komponente HomePage wird von layout.tsx umgeben, welches Kopf- und Fußzeilen sowie andere gemeinsame Elemente enthalten kann.
Umgang mit anderen statischen Pfaden

Beispiel: /about Route

Dateistruktur:

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

Implementierung:

// app/about/page.tsx

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

Erklärung:

  • Routen-Definition: Die Datei page.tsx im Ordner about entspricht der Route /about.
  • Darstellung: Diese Komponente stellt den Inhalt der Seite /about dar.
Dynamische Routen

Dynamische Routen ermöglichen das Verarbeiten von Pfaden mit variablen Segmenten und erlauben es Anwendungen, Inhalte basierend auf Parametern wie IDs, slugs usw. anzuzeigen.

Beispiel: /posts/[id] Route

Dateistruktur:

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

Implementierung:

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

Erklärung:

  • Dynamic Segment: [id] bezeichnet ein dynamisches Segment in der Route und erfasst den id-Parameter aus der URL.
  • Accessing Parameters: Das params-Objekt enthält die dynamischen Parameter und ist innerhalb der Komponente zugänglich.
  • Route Matching: Jeder Pfad, der /posts/* entspricht, wie z. B. /posts/1, /posts/abc usw., wird von dieser Komponente behandelt.
Verschachtelte Routen

Next.js unterstützt verschachtelte Routen und ermöglicht hierarchische Routenkonstruktionen, die der Verzeichnisstruktur entsprechen.

Beispiel: /dashboard/settings/profile Route

Dateistruktur:

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

Implementierung:

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

Erklärung:

  • Tiefe Verschachtelung: Die Datei page.tsx innerhalb von dashboard/settings/profile/ entspricht der Route /dashboard/settings/profile.
  • Abbildung der Hierarchie: Die Verzeichnisstruktur bildet den URL-Pfad ab und erhöht damit die Wartbarkeit und Übersichtlichkeit.
Catch-All-Routen

Catch-All-Routen fangen mehrere verschachtelte Segmente oder unbekannte Pfade ab und ermöglichen so eine flexible Routenbehandlung.

Beispiel: /* Route

Dateistruktur:

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

Implementierung:

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

Erklärung:

  • Catch-All-Segment: [...slug] erfasst alle verbleibenden Pfadsegmente als Array.
  • Verwendung: Nützlich zur Verarbeitung dynamischer Routing-Szenarien wie benutzergenerierter Pfade, verschachtelter Kategorien usw.
  • Routenabgleich: Pfade wie /anything/here, /foo/bar/baz usw. werden von dieser Komponente verarbeitet.

Potenzielle clientseitige Schwachstellen

Obwohl Next.js eine sichere Grundlage bietet, können unsachgemäße Codierpraktiken Schwachstellen einführen. Wichtige clientseitige Schwachstellen sind:

Cross-Site Scripting (XSS)

XSS-Angriffe treten auf, wenn bösartige Skripte in vertrauenswürdige Websites eingeschleust werden. Angreifer können Skripte in den Browsern der Benutzer ausführen, Daten stehlen oder Aktionen im Namen des Benutzers durchführen.

Beispiel für verwundbaren Code:

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

Warum es anfällig ist: Die Verwendung von dangerouslySetInnerHTML mit nicht vertrauenswürdigen Eingaben ermöglicht es Angreifern, bösartige Skripte einzuschleusen.

Client-Side Template Injection

Tritt auf, wenn Benutzereingaben in Vorlagen nicht richtig verarbeitet werden, wodurch Angreifer Vorlagen oder Ausdrücke einschleusen und ausführen können.

Beispiel für anfälligen Code:

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

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

Warum es verwundbar ist: Wenn template oder data bösartigen Inhalt enthält, kann dies zur Ausführung von unbeabsichtigtem Code führen.

Client Path Traversal

Es handelt sich um eine Schwachstelle, die es Angreifern ermöglicht, clientseitige Pfade zu manipulieren, um unbeabsichtigte Aktionen auszuführen, wie z. B. Cross-Site Request Forgery (CSRF). Im Gegensatz zu server-side path traversal, das auf das Dateisystem des Servers abzielt, konzentriert sich CSPT darauf, clientseitige Mechanismen auszunutzen, um legitime API-Anfragen auf bösartige Endpunkte umzuleiten.

Beispiel für verwundbaren Code:

Eine Next.js-Anwendung ermöglicht Benutzern das Hoch- und Herunterladen von Dateien. Die Download-Funktion ist auf der Client-Seite implementiert, wo Benutzer den Dateipfad angeben können, um eine Datei herunterzuladen.

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

Angriffsszenario

  1. Attacker’s Objective: Einen CSRF-Angriff durchführen, um eine kritische Datei (z. B. admin/config.json) zu löschen, indem der filePath manipuliert wird.
  2. Ausnutzen von CSPT:
  • Malicious Input: Der Angreifer erstellt eine URL mit einem manipulierten filePath, z. B. ../deleteFile/config.json.
  • Resulting API Call: Der clientseitige Code macht eine Anfrage an /api/files/../deleteFile/config.json.
  • Server’s Handling: Wenn der Server den filePath nicht validiert, verarbeitet er die Anfrage und löscht oder legt möglicherweise sensible Dateien offen.
  1. Executing CSRF:
  • Crafted Link: Der Angreifer sendet dem Opfer einen Link oder bettet ein bösartiges Script ein, das die Download-Anfrage mit dem manipulierten filePath auslöst.
  • Outcome: Das Opfer führt die Aktion unwissentlich aus, was zu unautorisiertem Datei-Zugriff oder -Löschung führt.

Warum es anfällig ist

  • Fehlende Eingabevalidierung: Die Client-Seite erlaubt beliebige filePath-Eingaben, was path traversal ermöglicht.
  • Vertrauen in Client-Eingaben: Die serverseitige API vertraut und verarbeitet den filePath ohne Bereinigung.
  • Mögliche API-Aktionen: Wenn der API-Endpunkt zustandsändernde Aktionen (z. B. Dateien löschen oder ändern) ausführt, kann er über CSPT ausgenutzt werden.

Serverseitig in Next.js

Server-seitiges Rendering (SSR)

Seiten werden bei jeder Anfrage auf dem Server gerendert, sodass der Benutzer vollständig gerenderte HTML erhält. In diesem Fall sollten Sie einen eigenen benutzerdefinierten Server erstellen, um die Anfragen zu verarbeiten.

Use Cases:

  • Dynamische Inhalte, die sich häufig ändern.
  • SEO-Optimierung, da Suchmaschinen die vollständig gerenderte Seite crawlen können.

Implementierung:

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

Statische Seitengenerierung (SSG)

Seiten werden zur Build-Zeit vorgerendert, was zu schnelleren Ladezeiten und geringerer Serverauslastung führt.

Anwendungsfälle:

  • Inhalte, die sich nicht häufig ändern.
  • Blogs, Dokumentation, Marketingseiten.

Implementierung:

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

Serverlose Funktionen (API Routes)

Next.js ermöglicht das Erstellen von API-Endpunkten als serverlose Funktionen. Diese Funktionen laufen on-demand, ohne einen dedizierten Server zu benötigen.

Use Cases:

  • Verarbeitung von Formularübermittlungen.
  • Interaktion mit Datenbanken.
  • Datenverarbeitung oder Integration mit Drittanbieter-APIs.

Implementation:

Mit der Einführung des app directory in Next.js 13 sind Routing und API-Handling flexibler und leistungsfähiger geworden. Dieser moderne Ansatz stimmt eng mit dem dateibasierten Routing-System überein, bringt jedoch erweiterte Möglichkeiten mit sich, einschließlich Unterstützung für Server- und Client-Komponenten.

Basic Route Handler

Dateistruktur:

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

Implementierung:

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

Erklärung:

  • Speicherort: API-Routen befinden sich im Verzeichnis app/api/.
  • Dateibenennung: Jeder API-Endpunkt befindet sich in einem eigenen Ordner, der eine route.js- oder route.ts-Datei enthält.
  • Exportierte Funktionen: Anstelle eines einzelnen default-Exports werden spezifische HTTP-Methode-Funktionen (z. B. GET, POST) exportiert.
  • Response-Handling: Verwende den Response-Konstruktor, um Antworten zurückzugeben; das erlaubt mehr Kontrolle über Header und Statuscodes.

Wie man andere Pfade und Methoden behandelt:

Spezifische HTTP-Methoden behandeln

Next.js 13+ ermöglicht es, Handler für spezifische HTTP-Methoden innerhalb derselben route.js- oder route.ts-Datei zu definieren, was klareren und besser organisierten Code fördert.

Beispiel:

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

Erklärung:

  • Mehrere Exporte: Jede HTTP-Methode (GET, PUT, DELETE) hat ihre eigene exportierte Funktion.
  • Parameter: Das zweite Argument ermöglicht Zugriff auf Routenparameter über params.
  • Erweiterte Antworten: Größere Kontrolle über Response-Objekte, wodurch präzise Header- und Statuscode-Verwaltung möglich wird.
Catch-All- und verschachtelte Routen

Next.js 13+ unterstützt erweiterte Routing-Funktionen wie Catch-All-Routen und verschachtelte API-Routen, wodurch dynamischere und skalierbarere API-Strukturen möglich sind.

Catch-All-Route-Beispiel:

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

Erklärung:

  • Syntax: [...] bezeichnet ein Catch-all-Segment, das alle verschachtelten Pfade erfasst.
  • Verwendung: Nützlich für APIs, die unterschiedlich tiefe Routen oder dynamische Segmente verarbeiten müssen.

Beispiel für verschachtelte Routen:

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

Erklärung:

  • Tiefe Verschachtelung: Ermöglicht hierarchische API-Strukturen und spiegelt Ressourcenbeziehungen wider.
  • Parameterzugriff: Ermöglicht einfachen Zugriff auf mehrere Routenparameter über das params-Objekt.
Umgang mit API-Routen in Next.js 12 und früher

API-Routen im pages-Verzeichnis (Next.js 12 und früher)

Bevor Next.js 13 das app-Verzeichnis und erweiterte Routing-Funktionen einführte, wurden API-Routen hauptsächlich im pages-Verzeichnis definiert. Dieser Ansatz wird weiterhin häufig verwendet und in Next.js 12 und früheren Versionen unterstützt.

Grundlegende API-Route

Dateistruktur:

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

Implementierung:

javascriptCopy code// pages/api/hello.js

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

Erklärung:

  • Location: API routes befinden sich im Verzeichnis pages/api/.
  • Export: Verwende export default, um die Handler-Funktion zu definieren.
  • Function Signature: Der Handler erhält die Objekte req (HTTP request) und res (HTTP response).
  • Routing: Der Dateiname (hello.js) entspricht dem Endpoint /api/hello.

Dynamische API-Routen

Dateistruktur:

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

Implementierung:

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

Erklärung:

  • Dynamische Segmente: Eckige Klammern ([id].js) kennzeichnen dynamische Route-Segmente.
  • Zugriff auf Parameter: Verwende req.query.id, um auf den dynamischen Parameter zuzugreifen.
  • Umgang mit Methoden: Verwende bedingte Logik, um verschiedene HTTP-Methoden (GET, PUT, DELETE usw.) zu verarbeiten.

Umgang mit verschiedenen HTTP-Methoden

Während das grundlegende API-Route-Beispiel alle HTTP-Methoden innerhalb einer einzigen Funktion behandelt, kannst du deinen Code so strukturieren, dass jede Methode explizit behandelt wird, um bessere Übersichtlichkeit und Wartbarkeit zu erreichen.

Beispiel:

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

Beste Praktiken:

  • Trennung der Verantwortlichkeiten: Logik für verschiedene HTTP-Methoden klar trennen.
  • Konsistenz der Antworten: Stellen Sie konsistente Antwortstrukturen sicher, um die clientseitige Verarbeitung zu erleichtern.
  • Fehlerbehandlung: Nicht unterstützte Methoden und unerwartete Fehler sauber abfangen.

CORS-Konfiguration

Kontrollieren Sie, welche Origins auf Ihre API-Routen zugreifen können, um Cross-Origin Resource Sharing (CORS)-Schwachstellen zu mindern.

Schlechtes Konfigurationsbeispiel:

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

Beachte, dass CORS auch in allen API routes in der Datei middleware.ts konfiguriert werden kann:

// 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: '*': Erlaubt jeder Website, auf die API zuzugreifen, wodurch bösartige Seiten ohne Einschränkungen mit deiner API interagieren könnten.
  • Zu breite Methodenfreigabe: Das Zulassen aller Methoden kann Angreifern ermöglichen, unerwünschte Aktionen durchzuführen.

Wie Angreifer es ausnutzen:

Angreifer können bösartige Websites erstellen, die Anfragen an deine API senden und dabei Funktionen wie Datenabruf, Datenmanipulation oder das Auslösen unerwünschter Aktionen im Namen authentifizierter Nutzer missbrauchen.

CORS - Misconfigurations & Bypass

Server-Code-Offenlegung auf der Client-Seite

Es kann leicht passieren, dass man Code, der auf dem Server verwendet wird, auch im clientseitig exponierten Code nutzt. Der beste Weg, sicherzustellen, dass eine Code-Datei niemals auf der Client-Seite offengelegt wird, ist die Verwendung dieses Imports am Anfang der Datei:

import "server-only"

Wichtige Dateien und ihre Rollen

middleware.ts / middleware.js

Ort: Stammverzeichnis des Projekts oder innerhalb von src/.

Zweck: Führt Code in der serverseitigen serverlosen Funktion aus, bevor eine Anfrage verarbeitet wird, und ermöglicht Aufgaben wie Authentifizierung, Weiterleitungen oder das Anpassen von Antworten.

Ablauf:

  1. Eingehende Anfrage: Die Middleware fängt die Anfrage ab.
  2. Verarbeitung: Führt Operationen basierend auf der Anfrage aus (z. B. Authentifizierung prüfen).
  3. Antwortänderung: Kann die Antwort ändern oder die Kontrolle an den nächsten Handler weitergeben.

Beispielanwendungsfälle:

  • Weiterleitung nicht authentifizierter Benutzer.
  • Hinzufügen benutzerdefinierter Header.
  • Protokollieren von Anfragen.

Beispielkonfiguration:

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

Ort: Root des Projekts.

Zweck: Konfiguriert das Verhalten von Next.js, aktiviert oder deaktiviert Features, passt webpack-Konfigurationen an, setzt Umgebungsvariablen und konfiguriert mehrere Sicherheitsfunktionen.

Wichtige Sicherheitskonfigurationen:

Sicherheitsheader

Security headers erhöhen die Sicherheit Ihrer Anwendung, indem sie Browser anweisen, wie mit Inhalten umgegangen werden soll. Sie helfen, verschiedene Angriffe wie Cross-Site Scripting (XSS), Clickjacking und MIME type sniffing zu mindern:

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

Beispiele:

// 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...
],
},
]
},
}
Einstellungen zur Bildoptimierung

Next.js optimiert Bilder für die Performance, aber Fehlkonfigurationen können zu Sicherheitslücken führen, beispielsweise indem sie nicht vertrauenswürdigen Quellen erlauben, schädlichen Inhalt einzuschleusen.

Schlechtes Konfigurationsbeispiel:

// next.config.js

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

Problem:

  • '*': Ermöglicht das Laden von Bildern aus beliebigen externen Quellen, einschließlich nicht vertrauenswürdiger oder bösartiger Domains. Angreifer können Bilder hosten, die schädliche Payloads enthalten oder Benutzer irreführen.
  • Ein weiteres Problem kann sein, eine Domain zuzulassen, auf der jeder ein Bild hochladen kann (wie raw.githubusercontent.com)

How attackers abuse it:

Indem sie Bilder aus bösartigen Quellen einfügen, können Angreifer Phishing-Angriffe durchführen, irreführende Informationen anzeigen oder Schwachstellen in Image-Rendering-Bibliotheken ausnutzen.

Offenlegung von Umgebungsvariablen

Verwalte sensible Informationen wie API-Schlüssel und Datenbank-Zugangsdaten sicher, ohne sie an den Client preiszugeben.

a. Offenlegung sensibler Variablen

Schlechtes Konfigurationsbeispiel:

// 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: Ohne das Präfix NEXT_PUBLIC_ macht Next.js Variablen nicht auf der Client-Seite zugänglich. Wenn es jedoch fälschlicherweise mit dem Präfix versehen wird (z. B. NEXT_PUBLIC_SECRET_API_KEY), wird es auf der Client-Seite zugänglich.

How attackers abuse it:

Wenn sensible Variablen dem Client offengelegt sind, können Angreifer sie durch Inspektion des Client-Codes oder der Netzwerkrequests abrufen und so unbefugten Zugriff auf APIs, Datenbanken oder andere Dienste erlangen.

Weiterleitungen

Verwalten Sie URL-Weiterleitungen und -Umschreibungen innerhalb Ihrer Anwendung, sodass Benutzer korrekt weitergeleitet werden, ohne open redirect vulnerabilities einzuführen.

a. Open Redirect Vulnerability

Schlechtes Konfigurationsbeispiel:

// next.config.js

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

Problem:

  • Dynamisches Ziel: Ermöglicht es Benutzern, beliebige URLs anzugeben, wodurch open redirect attacks möglich sind.
  • Vertrauen auf Benutzereingaben: Weiterleitungen zu von Benutzern bereitgestellten URLs ohne Validierung können zu phishing, malware distribution oder credential theft führen.

Wie Angreifer es ausnutzen:

Angreifer können URLs konstruieren, die so aussehen, als kämen sie von Ihrer Domain, aber Benutzer auf bösartige Seiten weiterleiten. Zum Beispiel:

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

Benutzer, die der ursprünglichen Domain vertrauen, könnten unwissentlich zu schädlichen Websites navigieren.

Webpack-Konfiguration

Das Anpassen der Webpack-Konfigurationen für Ihre Next.js-Anwendung kann unbeabsichtigt Sicherheitslücken einführen, wenn es nicht vorsichtig gehandhabt wird.

a. Offenlegung sensibler Module

Schlechtes Konfigurationsbeispiel:

// next.config.js

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

Problem:

  • Sensible Pfade offenlegen: Das Aliasing sensibler Verzeichnisse und das Zulassen von clientseitigem Zugriff kann vertrauliche Informationen leak.
  • Secrets bündeln: Wenn sensible Dateien für den Client gebündelt werden, werden ihre Inhalte über source maps oder durch Inspektion des clientseitigen Codes zugänglich.

How attackers abuse it:

Angreifer können auf die Verzeichnisstruktur der Anwendung zugreifen oder sie rekonstruieren und dabei möglicherweise sensible Dateien oder Daten finden und ausnutzen.

pages/_app.js and pages/_document.js

pages/_app.js

Purpose: Überschreibt die Standard-App-Komponente und ermöglicht globalen Zustand, Styles und Layout-Komponenten.

Use Cases:

  • Globale CSS einbinden.
  • Layout-Wrapper hinzufügen.
  • Integration von State-Management-Bibliotheken.

Example:

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

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

export default MyApp

pages/_document.js

Zweck: Überschreibt das standardmäßige Document und ermöglicht die Anpassung der <html>- und <body>-Tags.

Anwendungsfälle:

  • Anpassen der <html>- oder <body>-Tags.
  • Hinzufügen von Meta-Tags oder benutzerdefinierten Skripten.
  • Einbinden von Drittanbieter-Schriftarten.

Beispiel:

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

Benutzerdefinierter Server (optional)

Zweck: Obwohl Next.js mit einem integrierten Server geliefert wird, können Sie einen benutzerdefinierten Server für fortgeschrittene Anwendungsfälle erstellen, z. B. für benutzerdefiniertes Routing oder zur Integration in vorhandene Backend-Services.

Hinweis: Die Verwendung eines benutzerdefinierten Servers kann die Deployment-Optionen einschränken, insbesondere auf Plattformen wie Vercel, die für den integrierten Server von Next.js optimiert sind.

Beispiel:

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

Zusätzliche Architektur- und Sicherheitsüberlegungen

Umgebungsvariablen und Konfiguration

Zweck: Vertrauliche Informationen und Konfigurationseinstellungen außerhalb der Codebasis verwalten.

Bewährte Vorgehensweisen:

  • Verwenden Sie .env-Dateien: Speichern Sie Variablen wie API-Schlüssel in .env.local (von der Versionskontrolle ausgeschlossen).
  • Greifen Sie sicher auf Variablen zu: Verwenden Sie process.env.VARIABLE_NAME, um auf Umgebungsvariablen zuzugreifen.
  • Offenbaren Sie niemals Geheimnisse im Client: Stellen Sie sicher, dass vertrauliche Variablen nur serverseitig verwendet werden.

Beispiel:

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

Hinweis: Um Variablen auf serverseitige Verwendung zu beschränken, lassen Sie sie aus dem env-Objekt weg oder versehen Sie sie mit dem Präfix NEXT_PUBLIC_, damit sie dem Client zugänglich sind.

Authentifizierung und Autorisierung

Ansatz:

  • Session-basierte Authentifizierung: Verwenden Sie Cookies, um Benutzersitzungen zu verwalten.
  • Token-basierte Authentifizierung: Implementieren Sie JWTs für zustandslose Authentifizierung.
  • Drittanbieter-Provider: Integrieren Sie OAuth-Provider (z. B. Google, GitHub) mit Bibliotheken wie next-auth.

Sicherheitspraktiken:

  • Sichere Cookies: Setzen Sie die Attribute HttpOnly, Secure und SameSite.
  • Passworthashing: Hashen Sie Passwörter immer, bevor Sie sie speichern.
  • Eingabevalidierung: Verhindern Sie Injection-Angriffe, indem Sie Eingaben validieren und bereinigen.

Beispiel:

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

Performance-Optimierung

Strategien:

  • Bildoptimierung: Verwenden Sie Next.js’ next/image-Komponente für automatische Bildoptimierung.
  • Code-Splitting: Nutzen Sie dynamische Imports, um Code zu splitten und die initiale Ladezeit zu reduzieren.
  • Caching: Implementieren Sie Caching-Strategien für API-Antworten und statische Assets.
  • Lazy Loading: Laden Sie Komponenten oder Assets nur, wenn sie benötigt werden.

Beispiel:

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

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

Next.js Server Actions Aufzählung (Hash zum Funktionsnamen über Source Maps)

Moderne Next.js verwendet “Server Actions”, die auf dem Server ausgeführt werden, aber vom Client aufgerufen werden. In Produktion sind diese Aufrufe undurchsichtig: alle POSTs landen auf einem gemeinsamen Endpunkt und werden durch einen build-spezifischen Hash unterschieden, der im Header Next-Action gesendet wird. Beispiel:

POST /
Next-Action: a9f8e2b4c7d1...

Wenn productionBrowserSourceMaps aktiviert ist, enthalten minimierte JS-Chunks Aufrufe zu createServerReference(...), die genug Struktur (plus zugehörige source maps) leak, um eine Zuordnung zwischen dem Action-Hash und dem ursprünglichen Funktionsnamen wiederherzustellen. Damit lassen sich Hashes, die in Next-Action beobachtet werden, in konkrete Ziele wie deleteUserAccount() oder exportFinancialData() übersetzen.

Extraktionsansatz (regex auf minimiertem JS + optionale source maps)

Durchsuche heruntergeladene JS-Chunks nach createServerReference und extrahiere den Hash sowie das Funktions-/Source-Symbol. Zwei nützliche Muster:

# Strict pattern for standard minification
createServerReference\)\"([a-f0-9]{40,})\",\w+\.callServer,void 0,\w+\.findSourceMapURL,\"([^\"]+)\"\)

# Flexible pattern handling various minification styles
createServerReference[^\"]*\"([a-f0-9]{40,})\"[^\"]*\"([^\"]+)\"\s*\)
  • Gruppe 1: server action hash (40+ hex chars)
  • Gruppe 2: Symbol oder Pfad, der, falls eine source map vorhanden ist, über die source map auf die ursprüngliche Funktion aufgelöst werden kann

Wenn das Script eine source map im Trailer-Kommentar //# sourceMappingURL=<...>.map angibt, lade diese und löse das Symbol/den Pfad auf den ursprünglichen Funktionsnamen auf.

Praktische Vorgehensweise

  • Passive Entdeckung beim Browsen: Requests mit Next-Action-Headern und JS-Chunk-URLs mitschneiden.
  • Die referenzierten JS-Bundles und die dazugehörigen *.map-Dateien abrufen (wenn vorhanden).
  • Den obenstehenden regex ausführen, um ein hash↔name-Wörterbuch zu erstellen.
  • Das Wörterbuch zum gezielten Testen verwenden:
    • Namensgetriebene Triage (z. B. transferFunds, exportFinancialData).
    • Coverage über Builds hinweg nach Funktionsname verfolgen (Hashes rotieren zwischen Builds).

Ausführen versteckter actions (template-basierte Anfrage)

Nimm ein gültiges POST, das im Proxy beobachtet wurde, als Vorlage und tausche den Next-Action-Wert, um eine andere entdeckte action anzusprechen:

# Before
Next-Action: a9f8e2b4c7d1

# After
Next-Action: b7e3f9a2d8c5

Im Repeater ausführen und die Autorisierung, Eingabevalidierung und Geschäftslogik von sonst nicht erreichbaren Aktionen testen.

Burp-Automatisierung

  • NextjsServerActionAnalyzer (Burp extension) automatisiert das oben Beschriebene in Burp:
  • Durchsucht die Proxy-History nach JS-Chunks, extrahiert createServerReference(...)-Einträge und parst bei Verfügbarkeit source maps.
  • Pflegt ein durchsuchbares Hash↔Funktionsnamen-Wörterbuch und entfernt Duplikate über Builds hinweg anhand des Funktionsnamens.
  • Kann ein gültiges Template-POST finden und einen zum Senden bereiten Repeater-Tab öffnen, in dem der Hash der Zielaktion eingesetzt ist.
  • Repo: https://github.com/Adversis/NextjsServerActionAnalyzer

Hinweise und Einschränkungen

  • Benötigt productionBrowserSourceMaps in der Produktion aktiviert, um Namen aus Bundles/source maps wiederherzustellen.
  • Die Offenlegung von Funktionsnamen ist für sich genommen keine Schwachstelle; nutze sie zur gezielten Suche und teste die Autorisierung jeder Aktion.

Referenzen

Tip

Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Lernen & üben Sie Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Unterstützen Sie HackTricks