NextJS

Tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks

Architecture générale d’une application Next.js

Structure de fichiers typique

Un projet Next.js standard suit une structure de fichiers et de répertoires spécifique qui facilite ses fonctionnalités comme le routage, les endpoints d’API et la gestion des ressources statiques. Voici une disposition typique:

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

Principaux répertoires et fichiers

  • public/: Héberge des ressources statiques telles que images, polices et autres fichiers. Les fichiers ici sont accessibles à la racine (/).
  • app/: Répertoire central pour les pages de votre application, layouts, components et routes API. Adopte le paradigme App Router, permettant des fonctionnalités avancées de routing et la ségrégation des composants serveur/client.
  • app/layout.tsx: Définit le layout racine de votre application, entourant toutes les pages et fournissant des éléments d’UI cohérents comme en-têtes, pieds de page et barres de navigation.
  • app/page.tsx: Sert de point d’entrée pour la route racine /, rendant la page d’accueil.
  • app/[route]/page.tsx: Gère les routes statiques et dynamiques. Chaque dossier dans app/ représente un segment de route, et le page.tsx à l’intérieur de ces dossiers correspond au composant de la route.
  • app/api/: Contient des routes API, vous permettant de créer des fonctions serverless qui traitent les requêtes HTTP. Ces routes remplacent le répertoire traditionnel pages/api.
  • app/components/: Contient des composants React réutilisables pouvant être utilisés à travers différentes pages et layouts.
  • app/styles/: Contient des fichiers CSS globaux et des CSS Modules pour le style scoped aux composants.
  • app/utils/: Inclut des fonctions utilitaires, modules d’aide et autre logique non-UI qui peut être partagée dans l’application.
  • .env.local: Stocke les variables d’environnement spécifiques à l’environnement de développement local. Ces variables ne sont pas commit au contrôle de version.
  • next.config.js: Personnalise le comportement de Next.js, incluant les configurations webpack, les variables d’environnement et les paramètres de sécurité.
  • tsconfig.json: Configure les options TypeScript du projet, activant le typage et d’autres fonctionnalités TypeScript.
  • package.json: Gère les dépendances du projet, les scripts et les métadonnées.
  • README.md: Fournit la documentation et les informations sur le projet, y compris les instructions d’installation, les directives d’utilisation et autres détails pertinents.
  • yarn.lock / package-lock.json: Verrouillent les dépendances du projet à des versions spécifiques, garantissant des installations cohérentes entre différents environnements.

Côté client dans Next.js

Routage basé sur les fichiers dans le répertoire app

Le répertoire app est la pierre angulaire du routing dans les dernières versions de Next.js. Il exploite le système de fichiers pour définir les routes, rendant la gestion des routes intuitive et évolutive.

Gestion du chemin racine /

Structure des fichiers :

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

Fichiers clés :

  • app/page.tsx : Gère les requêtes vers le chemin racine /.
  • app/layout.tsx : Définit la mise en page de l’application, englobant toutes les pages.

Implémentation :

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

Explication :

  • Définition de la route : Le fichier page.tsx situé directement dans le répertoire app correspond à la route /.
  • Rendu : Ce composant affiche le contenu de la page d’accueil.
  • Intégration du layout : Le composant HomePage est enveloppé par le layout.tsx, qui peut inclure des en-têtes, des pieds de page et d’autres éléments communs.
Gestion d'autres chemins statiques

Exemple : route /about

Structure du fichier :

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

Implémentation:

// app/about/page.tsx

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

Explication :

  • Définition de la route : Le fichier page.tsx à l’intérieur du dossier about correspond à la route /about.
  • Rendu : Ce composant rend le contenu de la page about.
Routes dynamiques

Les routes dynamiques permettent de gérer des chemins avec des segments variables, permettant aux applications d’afficher du contenu en fonction de paramètres tels que IDs, slugs, etc.

Exemple : la route /posts/[id]

Structure des fichiers :

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

Implémentation :

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

Explication :

  • Segment dynamique : [id] désigne un segment dynamique dans la route, capturant le paramètre id depuis l’URL.
  • Accès aux paramètres : L’objet params contient les paramètres dynamiques, accessibles depuis le composant.
  • Correspondance de route : Tout chemin correspondant à /posts/*, comme /posts/1, /posts/abc, etc., sera géré par ce composant.
Routes imbriquées

Next.js prend en charge les routes imbriquées, permettant des structures de routes hiérarchiques qui reflètent l’organisation des dossiers.

Exemple : route /dashboard/settings/profile

Structure de fichiers :

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

Implémentation:

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

Explication :

  • Imbrication profonde : Le fichier page.tsx à l’intérieur de dashboard/settings/profile/ correspond à la route /dashboard/settings/profile.
  • Représentation de la hiérarchie : La structure des répertoires reflète le chemin URL, améliorant la maintenabilité et la clarté.
Routes catch-all

Les routes catch-all gèrent plusieurs segments imbriqués ou des chemins inconnus, offrant de la flexibilité dans le traitement des routes.

Exemple : Route /*

File Structure:

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

Implémentation :

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

Explication:

  • Segment Catch-All: [...slug] capture tous les segments de chemin restants sous forme de tableau.
  • Utilisation: Utile pour gérer des scénarios de routage dynamique tels que des chemins générés par les utilisateurs, des catégories imbriquées, etc.
  • Correspondance des routes: Des chemins comme /anything/here, /foo/bar/baz, etc., sont gérés par ce composant.

Vulnérabilités potentielles côté client

Bien que Next.js fournisse une base sécurisée, des pratiques de codage inappropriées peuvent introduire des vulnérabilités. Les principales vulnérabilités côté client comprennent :

Cross-Site Scripting (XSS)

Les attaques XSS se produisent lorsque des scripts malveillants sont injectés dans des sites de confiance. Les attaquants peuvent exécuter des scripts dans le navigateur des utilisateurs, voler des données ou effectuer des actions au nom de l’utilisateur.

Exemple de code vulnérable:

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

Why It’s Vulnerable: L’utilisation de dangerouslySetInnerHTML avec des entrées non fiables permet aux attaquants d’injecter des scripts malveillants.

Client-Side Template Injection

Se produit lorsque les entrées utilisateur sont mal gérées dans les templates, permettant aux attaquants d’injecter et d’exécuter des templates ou des expressions.

Example of Vulnerable Code:

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

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

Pourquoi c’est vulnérable : Si template ou data contient du contenu malveillant, cela peut entraîner l’exécution de code non prévu.

Client Path Traversal

C’est une vulnérabilité qui permet aux attaquants de manipuler les chemins côté client pour effectuer des actions non prévues, telles que Cross-Site Request Forgery (CSRF). Contrairement à server-side path traversal, qui cible le système de fichiers du serveur, CSPT vise à exploiter les mécanismes côté client pour rediriger des requêtes API légitimes vers des endpoints malveillants.

Exemple de code vulnérable :

Une application Next.js permet aux utilisateurs de téléverser et de télécharger des fichiers. La fonctionnalité de téléchargement est implémentée côté client, où les utilisateurs peuvent spécifier le chemin du fichier à télécharger.

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

Scénario d’attaque

  1. Objectif de l’attaquant : Effectuer une attaque CSRF pour supprimer un fichier critique (par ex., admin/config.json) en manipulant le filePath.
  2. Exploitation de CSPT :
  • Entrée malveillante : L’attaquant fabrique une URL avec un filePath manipulé tel que ../deleteFile/config.json.
  • Appel API résultant : Le code côté client effectue une requête vers /api/files/../deleteFile/config.json.
  • Traitement côté serveur : Si le serveur ne valide pas le filePath, il traite la requête, supprimant potentiellement ou exposant des fichiers sensibles.
  1. Exécution du CSRF :
  • Lien forgé : L’attaquant envoie à la victime un lien ou intègre un script malveillant qui déclenche la requête de téléchargement avec le filePath manipulé.
  • Conséquence : La victime exécute involontairement l’action, entraînant un accès non autorisé ou la suppression de fichiers.

Pourquoi c’est vulnérable

  • Manque de validation des entrées : Le côté client accepte des entrées filePath arbitraires, permettant la traversal de chemin.
  • Confiance aux entrées client : L’API côté serveur fait confiance et traite le filePath sans le désinfecter.
  • Actions potentielles de l’API : Si le endpoint API effectue des actions modifiant l’état (par ex., supprimer, modifier des fichiers), il peut être exploité via CSPT.

Côté serveur dans Next.js

Server-Side Rendering (SSR)

Les pages sont rendues côté serveur à chaque requête, garantissant que l’utilisateur reçoit du HTML entièrement rendu. Dans ce cas, vous devriez créer votre propre serveur personnalisé pour traiter les requêtes.

Cas d’utilisation :

  • Contenu dynamique qui change fréquemment.
  • Optimisation SEO, car les moteurs de recherche peuvent crawler la page entièrement rendue.

Implémentation :

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

Les pages sont pré-rendues lors de la phase de build, ce qui entraîne des temps de chargement plus rapides et une charge serveur réduite.

Cas d’utilisation :

  • Contenu qui ne change pas fréquemment.
  • Blogs, documentation, pages marketing.

Implémentation :

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

Fonctions serverless (API Routes)

Next.js permet la création d’endpoints API sous forme de fonctions serverless. Ces fonctions s’exécutent à la demande sans nécessiter de serveur dédié.

Cas d’utilisation :

  • Traitement des soumissions de formulaires.
  • Interaction avec des bases de données.
  • Traitement de données ou intégration avec des API tierces.

Implémentation :

Avec l’introduction du répertoire app dans Next.js 13, le routage et la gestion des API sont devenus plus flexibles et puissants. Cette approche moderne s’aligne étroitement sur le système de routage basé sur les fichiers mais introduit des capacités améliorées, y compris le support des composants serveur et client.

Gestionnaire de route basique

File Structure:

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

Implémentation:

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

Explication :

  • Emplacement : Les routes API sont placées dans le répertoire app/api/.
  • Nom des fichiers : Chaque endpoint API réside dans son propre dossier contenant un fichier route.js ou route.ts.
  • Fonctions exportées : Plutôt qu’un export par défaut unique, des fonctions pour des méthodes HTTP spécifiques (p. ex. GET, POST) sont exportées.
  • Gestion des réponses : Utilisez le constructeur Response pour renvoyer des réponses, ce qui permet un meilleur contrôle des en-têtes et des codes d’état.

Comment gérer d’autres chemins et méthodes :

Gestion des méthodes HTTP spécifiques

Next.js 13+ vous permet de définir des handlers pour des méthodes HTTP spécifiques dans le même fichier route.js ou route.ts, ce qui favorise un code plus clair et mieux organisé.

Exemple :

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

Explication :

  • Exports multiples : Chaque méthode HTTP (GET, PUT, DELETE) a sa propre fonction exportée.
  • Paramètres : Le deuxième argument permet d’accéder aux paramètres de route via params.
  • Réponses améliorées : Contrôle accru des objets de réponse, permettant une gestion précise des en-têtes et des codes d’état.
Routes catch-all et imbriquées

Next.js 13+ prend en charge des fonctionnalités avancées de routage telles que les routes catch-all et les routes API imbriquées, permettant des structures d’API plus dynamiques et évolutives.

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

Explication :

  • Syntaxe : [...] désigne un segment catch-all, capturant tous les chemins imbriqués.
  • Utilisation : Utile pour les APIs qui doivent gérer des profondeurs de route variables ou des segments dynamiques.

Exemple de routes imbriquées :

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

Explication :

  • Imbrication profonde : Permet des structures d’API hiérarchiques, reflétant les relations entre ressources.
  • Accès aux paramètres : Accédez facilement à plusieurs paramètres de route via l’objet params.
Gestion des routes API dans Next.js 12 et versions antérieures

Routes API dans le répertoire pages (Next.js 12 et versions antérieures)

Avant que Next.js 13 n’introduise le répertoire app et des capacités de routage améliorées, les routes API étaient principalement définies dans le répertoire pages. Cette approche est toujours largement utilisée et prise en charge dans Next.js 12 et les versions antérieures.

Route API de base

Structure de fichiers :

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

Implémentation :

javascriptCopy code// pages/api/hello.js

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

Explication :

  • Emplacement : Les routes API se trouvent dans le répertoire pages/api/.
  • Export : Utilisez export default pour définir la fonction handler.
  • Signature de fonction : Le handler reçoit les objets req (requête HTTP) et res (réponse HTTP).
  • Routing : Le nom de fichier (hello.js) correspond à l’endpoint /api/hello.

Routes API dynamiques

Structure de fichiers :

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

Implémentation :

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

Explication :

  • Dynamic Segments : Les crochets ([id].js) indiquent des segments de route dynamiques.
  • Accessing Parameters : Utilisez req.query.id pour accéder au paramètre dynamique.
  • Handling Methods : Utilisez une logique conditionnelle pour traiter les différentes méthodes HTTP (GET, PUT, DELETE, etc).

Gestion des différentes méthodes HTTP

Bien que l’exemple de route API de base gère toutes les méthodes HTTP dans une seule fonction, vous pouvez structurer votre code pour traiter chaque méthode explicitement afin d’améliorer la clarté et la maintenabilité.

Exemple :

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

Bonnes pratiques :

  • Séparation des responsabilités : Séparez clairement la logique pour les différentes méthodes HTTP.
  • Cohérence des réponses : Assurez des structures de réponse cohérentes pour faciliter le traitement côté client.
  • Gestion des erreurs : Traitez de manière appropriée les méthodes non supportées et les erreurs inattendues.

Configuration CORS

Contrôlez quelles origines peuvent accéder à vos routes API afin d’atténuer les vulnérabilités Cross-Origin Resource Sharing (CORS).

Exemple de mauvaise configuration :

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

Notez que CORS peut également être configuré dans toutes les API routes dans le fichier 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
}

Problème :

  • Access-Control-Allow-Origin: '*': Permet à n’importe quel site web d’accéder à l’API, ce qui peut permettre à des sites malveillants d’interagir avec votre API sans restrictions.
  • Autorisation large des méthodes : Autoriser toutes les méthodes peut permettre à des attaquants d’effectuer des actions non désirées.

Comment les attaquants l’exploitent :

Les attaquants peuvent créer des sites web malveillants qui effectuent des requêtes vers votre API, abusant potentiellement de fonctionnalités telles que la récupération de données, la manipulation de données ou le déclenchement d’actions indésirables au nom d’utilisateurs authentifiés.

CORS - Misconfigurations & Bypass

Exposition du code serveur côté client

Il est facile d’utiliser du code destiné au serveur également dans du code exposé et exécuté côté client, la meilleure façon de s’assurer qu’un fichier de code n’est jamais exposé côté client est d’utiliser cet import au début du fichier :

import "server-only"

Fichiers clés et leurs rôles

middleware.ts / middleware.js

Emplacement: À la racine du projet ou dans src/.

Objectif: Exécute du code dans la fonction serverless côté serveur avant le traitement d’une requête, permettant des tâches comme l’authentification, les redirections ou la modification des réponses.

Flux d’exécution:

  1. Requête entrante: Le middleware intercepte la requête.
  2. Traitement: Exécute des opérations en fonction de la requête (par ex., vérifier l’authentification).
  3. Modification de la réponse: Peut modifier la réponse ou transférer le contrôle au handler suivant.

Exemples d’utilisation:

  • Rediriger les utilisateurs non authentifiés.
  • Ajouter des en-têtes personnalisés.
  • Enregistrer les requêtes.

Exemple de configuration:

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

Emplacement : Racine du projet.

But : Configure le comportement de Next.js, permet d’activer ou désactiver des fonctionnalités, personnaliser les configurations webpack, définir des variables d’environnement et configurer plusieurs fonctionnalités de sécurité.

Principales configurations de sécurité :

En-têtes de sécurité

Les en-têtes de sécurité renforcent la sécurité de votre application en indiquant aux navigateurs comment traiter le contenu. Ils aident à atténuer diverses attaques telles que Cross-Site Scripting (XSS), Clickjacking et le sniffing du type MIME :

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

Exemples :

// 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...
],
},
]
},
}
Paramètres d'optimisation d'images

Next.js optimise les images pour les performances, mais des mauvaises configurations peuvent conduire à des vulnérabilités de sécurité, par exemple en permettant à des sources non fiables d’injecter du contenu malveillant.

Exemple de mauvaise configuration :

// next.config.js

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

Problème :

  • '*' : Permet de charger des images depuis n’importe quelle source externe, y compris des domaines non fiables ou malveillants. Les attaquants peuvent héberger des images contenant des payloads malveillants ou du contenu qui induit les utilisateurs en erreur.
  • Un autre problème peut être d’autoriser un domaine où n’importe qui peut téléverser une image (comme raw.githubusercontent.com)

Comment les attaquants l’exploitent :

En injectant des images depuis des sources malveillantes, les attaquants peuvent mener des attaques de phishing, afficher des informations trompeuses, ou exploiter des vulnérabilités dans les bibliothèques de rendu d’images.

Environment Variables Exposure

Gérez les informations sensibles comme API keys et database credentials de manière sécurisée sans les exposer au client.

a. Exposition de variables sensibles

Mauvais exemple de configuration :

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

Problème :

  • SECRET_API_KEY : Sans le préfixe NEXT_PUBLIC_, Next.js n’expose pas les variables au client. Cependant, s’il est préfixé par erreur (par ex., NEXT_PUBLIC_SECRET_API_KEY), il devient accessible côté client.

Comment les attaquants l’exploitent :

Si des variables sensibles sont exposées au client, des attaquants peuvent les récupérer en inspectant le code côté client ou les requêtes réseau, obtenant ainsi un accès non autorisé aux APIs, bases de données ou autres services.

Redirections

Gérez les redirections et réécritures d’URL au sein de votre application, en veillant à ce que les utilisateurs soient dirigés correctement sans introduire d’open redirect vulnerabilities.

a. Open Redirect Vulnerability

Exemple de mauvaise configuration :

// next.config.js

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

Problème :

  • Destination dynamique : Permet aux utilisateurs de spécifier n’importe quelle URL, ce qui facilite les attaques d’open redirect.
  • Faire confiance aux entrées utilisateur : Les redirections vers des URLs fournies par les utilisateurs sans validation peuvent conduire au phishing, à la distribution de malware, ou au vol d’identifiants.

Comment les attaquants l’exploitent :

Les attaquants peuvent créer des URLs qui semblent provenir de votre domaine mais redirigent les utilisateurs vers des sites malveillants. Par exemple :

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

Les utilisateurs faisant confiance au domaine d’origine peuvent, sans le savoir, naviguer vers des sites Web malveillants.

Configuration Webpack

Personnalisez les configurations Webpack pour votre application Next.js, ce qui peut introduire involontairement des vulnérabilités de sécurité si cela n’est pas fait avec prudence.

a. Exposition de modules sensibles

Exemple de mauvaise configuration :

// next.config.js

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

Problème :

  • Exposing Sensitive Paths : La création d’alias de répertoires sensibles et l’autorisation d’accès côté client peuvent leak des informations confidentielles.
  • Bundling Secrets : Si des fichiers sensibles sont inclus dans le bundle destiné au client, leur contenu devient accessible via les source maps ou en inspectant le code côté client.

How attackers abuse it :

Les attaquants peuvent accéder à la structure des répertoires de l’application ou la reconstruire, trouvant potentiellement des fichiers ou des données sensibles à exploiter.

pages/_app.js et pages/_document.js

pages/_app.js

Objectif : Remplace le composant App par défaut, permettant l’état global, les styles et les composants de layout.

Cas d’utilisation :

  • Injection de CSS global.
  • Ajout de wrappers de layout.
  • Intégration de bibliothèques de gestion d’état.

Exemple :

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

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

export default MyApp

pages/_document.js

Objectif : Remplace le Document par défaut, permettant de personnaliser les balises HTML et Body.

Cas d’utilisation :

  • Modifier les balises <html> ou <body>.
  • Ajouter des balises meta ou des scripts personnalisés.
  • Intégrer des polices tierces.

Exemple :

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

Serveur personnalisé (optionnel)

Objectif : Bien que Next.js soit fourni avec un serveur intégré, vous pouvez créer un serveur personnalisé pour des cas d’utilisation avancés tels que le routage personnalisé ou l’intégration avec des services backend existants.

Remarque : L’utilisation d’un serveur personnalisé peut limiter les options de déploiement, en particulier sur des plateformes comme Vercel qui optimisent pour le serveur intégré de Next.js.

Exemple :

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

Considérations architecturales et de sécurité supplémentaires

Variables d’environnement et configuration

Objectif : Gérer les informations sensibles et les paramètres de configuration en dehors du code.

Bonnes pratiques :

  • Utilisez les fichiers .env : Stockez des variables comme des clés API dans .env.local (exclu du contrôle de version).
  • Accédez aux variables en toute sécurité : Utilisez process.env.VARIABLE_NAME pour accéder aux variables d’environnement.
  • N’exposez jamais de secrets côté client : Assurez-vous que les variables sensibles ne sont utilisées que côté serveur.

Exemple :

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

Remarque : Pour restreindre les variables au seul serveur, omettez-les de l’objet env ou préfixez-les par NEXT_PUBLIC_ pour les exposer côté client.

Authentification et autorisation

Approche :

  • Authentification basée sur les sessions : Utilisez des cookies pour gérer les sessions utilisateur.
  • Authentification par tokens : Implémentez des JWTs pour une authentification sans état.
  • Fournisseurs tiers : Intégrez des providers OAuth (p. ex. Google, GitHub) en utilisant des bibliothèques comme next-auth.

Bonnes pratiques de sécurité :

  • Cookies sécurisés : Définissez les attributs HttpOnly, Secure et SameSite.
  • Hachage des mots de passe : Hachez toujours les mots de passe avant de les stocker.
  • Validation des entrées : Prévenez les attaques par injection en validant et en assainissant les entrées.

Exemple :

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

Optimisation des performances

Stratégies:

  • Optimisation des images : Utilisez le composant next/image de Next.js pour l’optimisation automatique des images.
  • Découpage du code (Code Splitting) : Exploitez les dynamic imports pour scinder le code et réduire le temps de chargement initial.
  • Mise en cache : Mettez en place des stratégies de caching pour les réponses d’API et les assets statiques.
  • Chargement différé (Lazy Loading) : Chargez les composants ou les assets uniquement lorsqu’ils sont nécessaires.

Exemple :

// 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 to function name via source maps)

Les versions récentes de Next.js utilisent les « Server Actions » qui s’exécutent côté serveur mais sont invoquées depuis le client. En production, ces invocations sont opaques : toutes les requêtes POST aboutissent sur un endpoint commun et sont distinguées par un hash spécifique à la build envoyé dans l’en-tête Next-Action. Exemple:

POST /
Next-Action: a9f8e2b4c7d1...

Lorsque productionBrowserSourceMaps est activé, les minified JS chunks contiennent des appels à createServerReference(...) qui provoquent un leak suffisant (ainsi que les source maps associées) pour reconstituer une correspondance entre le hash de l’action et le nom de la fonction d’origine. Cela permet de traduire les hashes observés dans Next-Action en cibles concrètes comme deleteUserAccount() ou exportFinancialData().

Approche d’extraction (regex sur minified JS + source maps optionnels)

Recherchez dans les JS chunks téléchargés createServerReference et extrayez le hash ainsi que le symbole fonction/source. Deux motifs utiles :

# 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*\)
  • Groupe 1 : action côté serveur hash (40+ caractères hexadécimaux)
  • Groupe 2 : symbole ou chemin qui peut être résolu vers la fonction originale via la source map lorsqu’elle est présente

If the script advertises a source map (trailer comment //# sourceMappingURL=<...>.map), fetch it and resolve the symbol/path to the original function name.

Practical workflow

  • Passive discovery while browsing: capture requests with Next-Action headers and JS chunk URLs.
  • Fetch the referenced JS bundles and accompanying *.map files (when present).
  • Run the regex above to build a hash↔name dictionary.
  • Use the dictionary to target testing:
  • Name-driven triage (e.g., transferFunds, exportFinancialData).
  • Track coverage across builds by function name (hashes rotate across builds).

Exercising hidden actions (template-based request)

Take a valid POST observed in-proxy as a template and swap the Next-Action value to target another discovered action:

# Before
Next-Action: a9f8e2b4c7d1

# After
Next-Action: b7e3f9a2d8c5

Rejouer dans Repeater et tester l’autorisation, la validation des entrées et la logique métier d’actions autrement inaccessibles.

Automatisation Burp

  • NextjsServerActionAnalyzer (Burp extension) automatise ce qui précède dans Burp :
  • Explore l’historique du proxy pour les JS chunks, extrait les entrées createServerReference(...) et analyse les source maps quand elles sont disponibles.
  • Maintient un dictionnaire searchable hash↔function-name et déduplique entre les builds par nom de fonction.
  • Peut localiser un POST template valide et ouvrir un onglet Repeater prêt à l’envoi avec le hash de l’action cible remplacé.
  • Repo: https://github.com/Adversis/NextjsServerActionAnalyzer

Remarques et limitations

  • Nécessite productionBrowserSourceMaps activé en production pour récupérer les noms depuis les bundles/source maps.
  • La divulgation de function-name n’est pas une vulnérabilité en soi ; utilisez-la pour guider la découverte et tester l’autorisation de chaque 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) contain a critical server-side prototype pollution during Flight chunk deserialization. By crafting $ references inside a Flight payload an attacker can pivot from polluted prototypes to arbitrary JavaScript execution and then to OS command execution inside the Node.js process.

NodeJS - proto & prototype Pollution

Attack chain in Flight chunks

  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.

Squelette du 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" }
}
}

Cartographie de l’exposition des React Server Function

Les React Server Functions (RSF) sont toutes les fonctions qui incluent la directive 'use server';. Chaque form action, mutation ou fetch helper liée à l’une de ces fonctions devient un RSC Flight endpoint qui désérialisera volontiers des attacker-supplied payloads. Étapes de reconnaissance utiles dérivées des évaluations React2Shell :

  • Inventaire statique : recherchez la directive pour comprendre combien de RSF sont automatiquement exposés par le framework.
rg -n "'use server';" -g"*.{js,ts,jsx,tsx}" app/
  • App Router defaults: create-next-app active l’App Router et le répertoire app/ par défaut, ce qui transforme silencieusement chaque route en un endpoint compatible RSC. Les assets de l’App Router tels que /_next/static/chunks/app/ ou les réponses qui streament des chunks Flight via text/x-component constituent des empreintes visibles sur Internet.
  • Implicitly vulnerable RSC deployments: L’avis de React indique que les apps incluant le runtime RSC peuvent être exploitables même sans RSFs explicites, donc considérez comme suspect tout build utilisant react-server-dom-* 19.0.0–19.2.0.
  • Other frameworks bundling RSC: Vite RSC, Parcel RSC, React Router RSC preview, RedwoodSDK, Waku, etc. réutilisent le même serializer et héritent de la même surface d’attaque distante tant qu’ils n’intègrent pas des builds React patchés.

Version coverage (React2Shell)

  • react-server-dom-webpack, react-server-dom-parcel, react-server-dom-turbopack: vulnérable en 19.0.0, 19.1.0–19.1.1 et 19.2.0 ; corrigé en 19.0.1, 19.1.2 et 19.2.1 respectivement.
  • Next.js stable: les releases App Router 15.0.0–16.0.6 intègrent la stack RSC vulnérable. Les trains de patch 15.0.5 / 15.1.9 / 15.2.6 / 15.3.6 / 15.4.8 / 15.5.7 / 16.0.7 incluent des dépendances corrigées, donc tout build inférieur à ces versions est une cible de grande valeur.
  • Next.js canary: 14.3.0-canary.77+ embarque aussi le runtime buggy et ne dispose actuellement pas de canary corrigés, faisant de ces empreintes des candidats sérieux à l’exploitation.

Remote detection oracle

Assetnote’s react2shell-scanner envoie une requête Flight multipartite créée sur mesure vers des chemins candidats et observe le comportement côté serveur :

  • Default mode exécute un payload RCE déterministe (opération mathématique reflétée via X-Action-Redirect) prouvant l’exécution de code.
  • --safe-check mode altère volontairement le message Flight de sorte que les serveurs patchés retournent 200/400, tandis que les cibles vulnérables émettent des réponses HTTP/500 contenant la sous-chaîne E{"digest" dans le corps. Cette paire (500 + digest) est actuellement l’oracle distant le plus fiable publié par les défenseurs.
  • Les switches intégrés --waf-bypass, --vercel-waf-bypass, et --windows ajustent la mise en page du payload, préfixent des données inutiles, ou remplacent les commandes OS afin que vous puissiez sonder des assets réels sur 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

Références

Tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks