NextJS
Reading time: 25 minutes
tip
Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Support HackTricks
- Confira os planos de assinatura!
- Junte-se ao 💬 grupo do Discord ou ao grupo do telegram ou siga-nos no Twitter 🐦 @hacktricks_live.
- Compartilhe truques de hacking enviando PRs para o HackTricks e HackTricks Cloud repositórios do github.
Arquitetura Geral de uma Aplicação Next.js
Estrutura de Arquivos Típica
Um projeto padrão Next.js segue uma estrutura específica de arquivos e diretórios que facilita seus recursos, como roteamento, endpoints de API e gerenciamento de ativos estáticos. Aqui está um layout 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
Diretórios e Arquivos Principais
- public/: Hospeda ativos estáticos, como imagens, fontes e outros arquivos. Os arquivos aqui são acessíveis no caminho raiz (
/
). - app/: Diretório central para as páginas, layouts, componentes e rotas da API da sua aplicação. Abrange o paradigma App Router, permitindo recursos avançados de roteamento e segregação de componentes servidor-cliente.
- app/layout.tsx: Define o layout raiz para sua aplicação, envolvendo todas as páginas e fornecendo elementos de UI consistentes, como cabeçalhos, rodapés e barras de navegação.
- app/page.tsx: Serve como o ponto de entrada para a rota raiz
/
, renderizando a página inicial. - app/[route]/page.tsx: Lida com rotas estáticas e dinâmicas. Cada pasta dentro de
app/
representa um segmento de rota, epage.tsx
dentro dessas pastas corresponde ao componente da rota. - app/api/: Contém rotas da API, permitindo que você crie funções serverless que manipulam requisições HTTP. Essas rotas substituem o diretório tradicional
pages/api
. - app/components/: Abriga componentes React reutilizáveis que podem ser utilizados em diferentes páginas e layouts.
- app/styles/: Contém arquivos CSS globais e CSS Modules para estilização com escopo de componente.
- app/utils/: Inclui funções utilitárias, módulos auxiliares e outras lógicas não relacionadas à UI que podem ser compartilhadas pela aplicação.
- .env.local: Armazena variáveis de ambiente específicas para o ambiente de desenvolvimento local. Essas variáveis não são comprometidas no controle de versão.
- next.config.js: Personaliza o comportamento do Next.js, incluindo configurações do webpack, variáveis de ambiente e configurações de segurança.
- tsconfig.json: Configura as definições do TypeScript para o projeto, habilitando verificação de tipos e outros recursos do TypeScript.
- package.json: Gerencia as dependências do projeto, scripts e metadados.
- README.md: Fornece documentação e informações sobre o projeto, incluindo instruções de configuração, diretrizes de uso e outros detalhes relevantes.
- yarn.lock / package-lock.json: Bloqueia as dependências do projeto em versões específicas, garantindo instalações consistentes em diferentes ambientes.
Lado do Cliente no Next.js
Roteamento Baseado em Arquivos no Diretório app
O diretório app
é a pedra angular do roteamento nas versões mais recentes do Next.js. Ele aproveita o sistema de arquivos para definir rotas, tornando o gerenciamento de rotas intuitivo e escalável.
Manipulando o Caminho Raiz /
Estrutura de Arquivos:
my-nextjs-app/
├── app/
│ ├── layout.tsx
│ └── page.tsx
├── public/
├── next.config.js
└── ...
Arquivos Chave:
app/page.tsx
: Manipula solicitações para o caminho raiz/
.app/layout.tsx
: Define o layout para a aplicação, envolvendo todas as páginas.
Implementação:
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>
);
}
Explicação:
- Definição de Rota: O arquivo
page.tsx
diretamente sob o diretórioapp
corresponde à rota/
. - Renderização: Este componente renderiza o conteúdo da página inicial.
- Integração de Layout: O componente
HomePage
é envolto pelolayout.tsx
, que pode incluir cabeçalhos, rodapés e outros elementos comuns.
Tratando Outros Caminhos Estáticos
Exemplo: Rota /about
Estrutura de Arquivos:
arduinoCopy codemy-nextjs-app/
├── app/
│ ├── about/
│ │ └── page.tsx
│ ├── layout.tsx
│ └── page.tsx
├── public/
├── next.config.js
└── ...
Implementação:
// app/about/page.tsx
export default function AboutPage() {
return (
<div>
<h1>About Us</h1>
<p>Learn more about our mission and values.</p>
</div>
)
}
Explicação:
- Definição de Rota: O arquivo
page.tsx
dentro da pastaabout
corresponde à rota/about
. - Renderização: Este componente renderiza o conteúdo da página sobre.
Rotas Dinâmicas
Rotas dinâmicas permitem o manuseio de caminhos com segmentos variáveis, permitindo que aplicações exibam conteúdo com base em parâmetros como IDs, slugs, etc.
Exemplo: Rota /posts/[id]
Estrutura de Arquivo:
arduinoCopy codemy-nextjs-app/
├── app/
│ ├── posts/
│ │ └── [id]/
│ │ └── page.tsx
│ ├── layout.tsx
│ └── page.tsx
├── public/
├── next.config.js
└── ...
Implementação:
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>
);
}
Explicação:
- Segmento Dinâmico:
[id]
denota um segmento dinâmico na rota, capturando o parâmetroid
da URL. - Acessando Parâmetros: O objeto
params
contém os parâmetros dinâmicos, acessíveis dentro do componente. - Correspondência de Rota: Qualquer caminho que corresponda a
/posts/*
, como/posts/1
,/posts/abc
, etc., será tratado por este componente.
Rotas Aninhadas
Next.js suporta roteamento aninhado, permitindo estruturas de rota hierárquicas que refletem o layout do diretório.
Exemplo: Rota /dashboard/settings/profile
Estrutura de Arquivos:
arduinoCopy codemy-nextjs-app/
├── app/
│ ├── dashboard/
│ │ ├── settings/
│ │ │ └── profile/
│ │ │ └── page.tsx
│ │ └── page.tsx
│ ├── layout.tsx
│ └── page.tsx
├── public/
├── next.config.js
└── ...
Implementação:
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>
);
}
Explicação:
- Aninhamento Profundo: O arquivo
page.tsx
dentro dedashboard/settings/profile/
corresponde à rota/dashboard/settings/profile
. - Reflexão de Hierarquia: A estrutura de diretórios reflete o caminho da URL, melhorando a manutenibilidade e clareza.
Rotas Catch-All
Rotas catch-all lidam com múltiplos segmentos aninhados ou caminhos desconhecidos, proporcionando flexibilidade no manuseio de rotas.
Exemplo: Rota /*
Estrutura de Arquivos:
my-nextjs-app/
├── app/
│ ├── [..slug]/
│ │ └── page.tsx
│ ├── layout.tsx
│ └── page.tsx
├── public/
├── next.config.js
└── ...
Implementação:
// 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>
)
}
Explicação:
- Segmento Catch-All:
[...slug]
captura todos os segmentos de caminho restantes como um array. - Uso: Útil para lidar com cenários de roteamento dinâmico, como caminhos gerados por usuários, categorias aninhadas, etc.
- Correspondência de Rota: Caminhos como
/anything/here
,/foo/bar/baz
, etc., são tratados por este componente.
Potenciais Vulnerabilidades do Lado do Cliente
Embora o Next.js forneça uma base segura, práticas de codificação inadequadas podem introduzir vulnerabilidades. As principais vulnerabilidades do lado do cliente incluem:
Cross-Site Scripting (XSS)
Os ataques XSS ocorrem quando scripts maliciosos são injetados em sites confiáveis. Os atacantes podem executar scripts nos navegadores dos usuários, roubando dados ou realizando ações em nome do usuário.
Exemplo de Código Vulnerável:
// Dangerous: Injecting user input directly into HTML
function Comment({ userInput }) {
return <div dangerouslySetInnerHTML={{ __html: userInput }} />
}
Por que é vulnerável: Usar dangerouslySetInnerHTML
com entrada não confiável permite que atacantes injetem scripts maliciosos.
Injeção de Template do Lado do Cliente
Ocorre quando as entradas do usuário são manipuladas de forma inadequada em templates, permitindo que atacantes injetem e executem templates ou expressões.
Exemplo de Código Vulnerável:
import React from "react"
import ejs from "ejs"
function RenderTemplate({ template, data }) {
const html = ejs.render(template, data)
return <div dangerouslySetInnerHTML={{ __html: html }} />
}
Por que é vulnerável: Se template
ou data
incluir conteúdo malicioso, isso pode levar à execução de código não intencional.
Travessia de Caminho do Cliente
É uma vulnerabilidade que permite que atacantes manipulem caminhos do lado do cliente para realizar ações não intencionais, como Cross-Site Request Forgery (CSRF). Ao contrário da travessia de caminho do lado do servidor, que visa o sistema de arquivos do servidor, a CSPT foca em explorar mecanismos do lado do cliente para redirecionar solicitações de API legítimas para pontos finais maliciosos.
Exemplo de Código Vulnerável:
Uma aplicação Next.js permite que os usuários façam upload e download de arquivos. O recurso de download é implementado no lado do cliente, onde os usuários podem especificar o caminho do arquivo a ser baixado.
// 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>
)
}
Cenário de Ataque
- Objetivo do Atacante: Realizar um ataque CSRF para deletar um arquivo crítico (por exemplo,
admin/config.json
) manipulando ofilePath
. - Explorando CSPT:
- Entrada Maliciosa: O atacante cria uma URL com um
filePath
manipulado, como../deleteFile/config.json
. - Chamada de API Resultante: O código do lado do cliente faz uma solicitação para
/api/files/../deleteFile/config.json
. - Tratamento pelo Servidor: Se o servidor não validar o
filePath
, ele processa a solicitação, potencialmente deletando ou expondo arquivos sensíveis.
- Executando CSRF:
- Link Criado: O atacante envia à vítima um link ou incorpora um script malicioso que aciona a solicitação de download com o
filePath
manipulado. - Resultado: A vítima executa a ação sem saber, levando ao acesso não autorizado ou exclusão de arquivos.
Por Que É Vulnerável
- Falta de Validação de Entrada: O lado do cliente permite entradas arbitrárias de
filePath
, possibilitando a travessia de caminho. - Confiando nas Entradas do Cliente: A API do lado do servidor confia e processa o
filePath
sem sanitização. - Ações Potenciais da API: Se o endpoint da API realiza ações que alteram o estado (por exemplo, deletar, modificar arquivos), pode ser explorado via CSPT.
Lado do Servidor em Next.js
Renderização do Lado do Servidor (SSR)
As páginas são renderizadas no servidor a cada solicitação, garantindo que o usuário receba HTML totalmente renderizado. Neste caso, você deve criar seu próprio servidor personalizado para processar as solicitações.
Casos de Uso:
- Conteúdo dinâmico que muda com frequência.
- Otimização de SEO, pois os mecanismos de busca podem rastrear a página totalmente renderizada.
Implementação:
// 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
Geração de Site Estático (SSG)
As páginas são pré-renderizadas no momento da construção, resultando em tempos de carregamento mais rápidos e carga reduzida no servidor.
Casos de Uso:
- Conteúdo que não muda com frequência.
- Blogs, documentação, páginas de marketing.
Implementação:
// 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
Funções Serverless (Rotas de API)
Next.js permite a criação de endpoints de API como funções serverless. Essas funções são executadas sob demanda, sem a necessidade de um servidor dedicado.
Casos de Uso:
- Manipulação de envios de formulários.
- Interação com bancos de dados.
- Processamento de dados ou integração com APIs de terceiros.
Implementação:
Com a introdução do diretório app
no Next.js 13, o roteamento e o manuseio de APIs se tornaram mais flexíveis e poderosos. Essa abordagem moderna se alinha de perto com o sistema de roteamento baseado em arquivos, mas introduz capacidades aprimoradas, incluindo suporte para componentes de servidor e cliente.
Manipulador de Rota Básico
Estrutura de Arquivos:
my-nextjs-app/
├── app/
│ └── api/
│ └── hello/
│ └── route.js
├── package.json
└── ...
Implementação:
// 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))
Explicação:
- Localização: As rotas da API estão localizadas no diretório
app/api/
. - Nomeação de Arquivos: Cada endpoint da API reside em sua própria pasta contendo um arquivo
route.js
ouroute.ts
. - Funções Exportadas: Em vez de uma única exportação padrão, funções específicas de métodos HTTP (por exemplo,
GET
,POST
) são exportadas. - Manipulação de Respostas: Use o construtor
Response
para retornar respostas, permitindo mais controle sobre cabeçalhos e códigos de status.
Como lidar com outros caminhos e métodos:
Manipulação de Métodos HTTP Específicos
Next.js 13+ permite que você defina manipuladores para métodos HTTP específicos dentro do mesmo arquivo route.js
ou route.ts
, promovendo um código mais claro e organizado.
Exemplo:
// 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" },
})
}
Explicação:
- Múltiplas Exportações: Cada método HTTP (
GET
,PUT
,DELETE
) tem sua própria função exportada. - Parâmetros: O segundo argumento fornece acesso aos parâmetros de rota via
params
. - Respostas Aprimoradas: Maior controle sobre objetos de resposta, permitindo gerenciamento preciso de cabeçalhos e códigos de status.
Rotas Catch-All e Aninhadas
Next.js 13+ suporta recursos avançados de roteamento, como rotas catch-all e rotas de API aninhadas, permitindo estruturas de API mais dinâmicas e escaláveis.
Exemplo de Rota 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" },
})
}
Explicação:
- Sintaxe:
[...]
denota um segmento abrangente, capturando todos os caminhos aninhados. - Uso: Útil para APIs que precisam lidar com profundidades de rota variadas ou segmentos dinâmicos.
Exemplo de Rotas Aninhadas:
// 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" },
}
)
}
Explicação:
- Aninhamento Profundo: Permite estruturas de API hierárquicas, refletindo relacionamentos de recursos.
- Acesso a Parâmetros: Acesso fácil a múltiplos parâmetros de rota via o objeto
params
.
Manipulando rotas de API no Next.js 12 e versões anteriores
Rotas de API no Diretório pages
(Next.js 12 e versões anteriores)
Antes que o Next.js 13 introduzisse o diretório app
e melhorasse as capacidades de roteamento, as rotas de API eram definidas principalmente dentro do diretório pages
. Essa abordagem ainda é amplamente utilizada e suportada no Next.js 12 e versões anteriores.
Rota de API Básica
Estrutura de Arquivos:
goCopy codemy-nextjs-app/
├── pages/
│ └── api/
│ └── hello.js
├── package.json
└── ...
Implementação:
javascriptCopy code// pages/api/hello.js
export default function handler(req, res) {
res.status(200).json({ message: 'Hello, World!' });
}
Explicação:
- Localização: As rotas da API residem no diretório
pages/api/
. - Exportação: Use
export default
para definir a função manipuladora. - Assinatura da Função: O manipulador recebe os objetos
req
(requisição HTTP) eres
(resposta HTTP). - Roteamento: O nome do arquivo (
hello.js
) mapeia para o endpoint/api/hello
.
Rotas de API Dinâmicas
Estrutura de Arquivos:
bashCopy codemy-nextjs-app/
├── pages/
│ └── api/
│ └── users/
│ └── [id].js
├── package.json
└── ...
Implementação:
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`);
}
}
Explicação:
- Segmentos Dinâmicos: Colchetes (
[id].js
) denotam segmentos de rota dinâmicos. - Acessando Parâmetros: Use
req.query.id
para acessar o parâmetro dinâmico. - Tratando Métodos: Utilize lógica condicional para lidar com diferentes métodos HTTP (
GET
,PUT
,DELETE
, etc.).
Tratando Diferentes Métodos HTTP
Enquanto o exemplo básico de rota da API lida com todos os métodos HTTP dentro de uma única função, você pode estruturar seu código para lidar com cada método explicitamente para melhor clareza e manutenibilidade.
Exemplo:
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`);
}
}
Melhores Práticas:
- Separação de Preocupações: Separe claramente a lógica para diferentes métodos HTTP.
- Consistência de Resposta: Garanta estruturas de resposta consistentes para facilitar o manuseio do lado do cliente.
- Tratamento de Erros: Lide graciosamente com métodos não suportados e erros inesperados.
Configuração de CORS
Controle quais origens podem acessar suas rotas de API, mitigando vulnerabilidades de Compartilhamento de Recursos de Origem Cruzada (CORS).
Exemplo de Configuração Ruim:
// 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",
},
})
}
Observe que CORS também pode ser configurado em todas as rotas da API dentro do arquivo 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 qualquer site acesse a API, potencialmente permitindo que sites maliciosos interajam com sua API sem restrições.- Permissão Ampla de Métodos: Permitir todos os métodos pode habilitar atacantes a realizar ações indesejadas.
Como os atacantes exploram isso:
Os atacantes podem criar sites maliciosos que fazem solicitações à sua API, potencialmente abusando de funcionalidades como recuperação de dados, manipulação de dados ou acionamento de ações indesejadas em nome de usuários autenticados.
CORS - Misconfigurations & Bypass
Exposição de código do servidor no lado do cliente
É fácil usar código usado pelo servidor também no código exposto e usado pelo lado do cliente, a melhor maneira de garantir que um arquivo de código nunca seja exposto no lado do cliente é usando esta importação no início do arquivo:
import "server-only"
Arquivos Chave e Seus Papéis
middleware.ts
/ middleware.js
Localização: Raiz do projeto ou dentro de src/
.
Propósito: Executa código na função serverless do lado do servidor antes que uma solicitação seja processada, permitindo tarefas como autenticação, redirecionamentos ou modificação de respostas.
Fluxo de Execução:
- Solicitação de Entrada: O middleware intercepta a solicitação.
- Processamento: Realiza operações com base na solicitação (por exemplo, verificar autenticação).
- Modificação da Resposta: Pode alterar a resposta ou passar o controle para o próximo manipulador.
Casos de Uso Exemplares:
- Redirecionar usuários não autenticados.
- Adicionar cabeçalhos personalizados.
- Registrar solicitações.
Configuração de Exemplo:
// 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
Localização: Raiz do projeto.
Propósito: Configura o comportamento do Next.js, habilitando ou desabilitando recursos, personalizando configurações do webpack, definindo variáveis de ambiente e configurando vários recursos de segurança.
Configurações de Segurança Chave:
Headers de Segurança
Headers de segurança aumentam a segurança da sua aplicação, instruindo os navegadores sobre como lidar com o conteúdo. Eles ajudam a mitigar vários ataques, como Cross-Site Scripting (XSS), Clickjacking e sniffing de tipo MIME:
- Content Security Policy (CSP)
- X-Frame-Options
- X-Content-Type-Options
- Strict-Transport-Security (HSTS)
- Referrer Policy
Exemplos:
// 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...
],
},
]
},
}
Configurações de Otimização de Imagem
Next.js otimiza imagens para desempenho, mas configurações incorretas podem levar a vulnerabilidades de segurança, como permitir que fontes não confiáveis injetem conteúdo malicioso.
Exemplo de Configuração Ruim:
// next.config.js
module.exports = {
images: {
domains: ["*"], // Allows images from any domain
},
}
Problema:
'*'
: Permite que imagens sejam carregadas de qualquer fonte externa, incluindo domínios não confiáveis ou maliciosos. Ataque pode hospedar imagens contendo cargas úteis maliciosas ou conteúdo que engana os usuários.- Outro problema pode ser permitir um domínio onde qualquer um pode fazer upload de uma imagem (como
raw.githubusercontent.com
)
Como os atacantes abusam disso:
Ao injetar imagens de fontes maliciosas, os atacantes podem realizar ataques de phishing, exibir informações enganosas ou explorar vulnerabilidades em bibliotecas de renderização de imagens.
Exposição de Variáveis de Ambiente
Gerencie informações sensíveis como chaves de API e credenciais de banco de dados de forma segura, sem expô-las ao cliente.
a. Expondo Variáveis Sensíveis
Exemplo de Configuração Ruim:
// 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
: Sem o prefixoNEXT_PUBLIC_
, o Next.js não expõe variáveis para o cliente. No entanto, se prefixado por engano (por exemplo,NEXT_PUBLIC_SECRET_API_KEY
), torna-se acessível no lado do cliente.
Como os atacantes abusam disso:
Se variáveis sensíveis forem expostas ao cliente, os atacantes podem recuperá-las inspecionando o código do lado do cliente ou as requisições de rede, obtendo acesso não autorizado a APIs, bancos de dados ou outros serviços.
Redirecionamentos
Gerencie redirecionamentos e reescritas de URL dentro de sua aplicação, garantindo que os usuários sejam direcionados adequadamente sem introduzir vulnerabilidades de redirecionamento aberto.
a. Vulnerabilidade de Redirecionamento Aberto
Exemplo de Configuração Ruim:
// 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 que os usuários especifiquem qualquer URL, possibilitando ataques de redirecionamento aberto.
- Confiando na Entrada do Usuário: Redirecionar para URLs fornecidas pelos usuários sem validação pode levar a phishing, distribuição de malware ou roubo de credenciais.
Como os atacantes abusam disso:
Os atacantes podem criar URLs que parecem originar do seu domínio, mas redirecionam os usuários para sites maliciosos. Por exemplo:
https://yourdomain.com/redirect?url=https://malicious-site.com
Os usuários que confiam no domínio original podem, sem saber, navegar para sites prejudiciais.
Configuração do Webpack
Personalize as configurações do Webpack para sua aplicação Next.js, que podem inadvertidamente introduzir vulnerabilidades de segurança se não forem tratadas com cautela.
a. Expondo Módulos Sensíveis
Exemplo de Configuração Ruim:
// next.config.js
module.exports = {
webpack: (config, { isServer }) => {
if (!isServer) {
config.resolve.alias["@sensitive"] = path.join(__dirname, "secret-folder")
}
return config
},
}
Problema:
- Expondo Caminhos Sensíveis: Alias de diretórios sensíveis e permitindo acesso do lado do cliente pode vazar informações confidenciais.
- Agrupando Segredos: Se arquivos sensíveis forem agrupados para o cliente, seu conteúdo se torna acessível através de mapas de origem ou inspecionando o código do lado do cliente.
Como os atacantes abusam disso:
Os atacantes podem acessar ou reconstruir a estrutura de diretórios da aplicação, potencialmente encontrando e explorando arquivos ou dados sensíveis.
pages/_app.js
e pages/_document.js
pages/_app.js
Propósito: Substitui o componente App padrão, permitindo estado global, estilos e componentes de layout.
Casos de Uso:
- Injetando CSS global.
- Adicionando wrappers de layout.
- Integrando bibliotecas de gerenciamento de estado.
Exemplo:
// pages/_app.js
import "../styles/globals.css"
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
export default MyApp
pages/_document.js
Propósito: Substitui o Documento padrão, permitindo a personalização das tags HTML e Body.
Casos de Uso:
- Modificando as tags
<html>
ou<body>
. - Adicionando meta tags ou scripts personalizados.
- Integrando fontes de terceiros.
Exemplo:
// 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: Embora o Next.js venha com um servidor embutido, você pode criar um servidor personalizado para casos de uso avançados, como roteamento personalizado ou integração com serviços de backend existentes.
Nota: Usar um servidor personalizado pode limitar as opções de implantação, especialmente em plataformas como Vercel que otimizam para o servidor embutido do Next.js.
Exemplo:
// 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")
})
})
Considerações Adicionais de Arquitetura e Segurança
Variáveis de Ambiente e Configuração
Propósito: Gerenciar informações sensíveis e configurações fora da base de código.
Melhores Práticas:
- Use Arquivos
.env
: Armazene variáveis como chaves de API em.env.local
(excluído do controle de versão). - Acesse Variáveis de Forma Segura: Use
process.env.VARIABLE_NAME
para acessar variáveis de ambiente. - Nunca Exponha Segredos no Cliente: Garanta que variáveis sensíveis sejam usadas apenas no lado do servidor.
Exemplo:
// 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 variáveis apenas ao lado do servidor, omita-as do objeto env
ou prefixe-as com NEXT_PUBLIC_
para exposição no cliente.
Autenticação e Autorização
Abordagem:
- Autenticação Baseada em Sessão: Use cookies para gerenciar sessões de usuários.
- Autenticação Baseada em Token: Implemente JWTs para autenticação sem estado.
- Provedores de Terceiros: Integre-se com provedores OAuth (por exemplo, Google, GitHub) usando bibliotecas como
next-auth
.
Práticas de Segurança:
- Cookies Seguros: Defina os atributos
HttpOnly
,Secure
eSameSite
. - Hashing de Senhas: Sempre faça hash das senhas antes de armazená-las.
- Validação de Entrada: Previna ataques de injeção validando e sanitizando entradas.
Exemplo:
// 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" })
}
}
Otimização de Desempenho
Estratégias:
- Otimização de Imagem: Use o componente
next/image
do Next.js para otimização automática de imagens. - Divisão de Código: Aproveite as importações dinâmicas para dividir o código e reduzir os tempos de carregamento iniciais.
- Cache: Implemente estratégias de cache para respostas de API e ativos estáticos.
- Carregamento Sob Demanda: Carregue componentes ou ativos apenas quando forem necessários.
Exemplo:
// Dynamic Import with Code Splitting
import dynamic from "next/dynamic"
const HeavyComponent = dynamic(() => import("../components/HeavyComponent"), {
loading: () => <p>Loading...</p>,
})
tip
Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Support HackTricks
- Confira os planos de assinatura!
- Junte-se ao 💬 grupo do Discord ou ao grupo do telegram ou siga-nos no Twitter 🐦 @hacktricks_live.
- Compartilhe truques de hacking enviando PRs para o HackTricks e HackTricks Cloud repositórios do github.