Este artigo consolida falhas críticas que identifiquei em auditorias de segurança de aplicações SaaS. O risco nestes cenários não é meramente técnico: envolve prejuízos financeiros diretos, danos à reputação e sérias implicações jurídicas na LGPD. O objetivo aqui é documentar a engenharia de segurança de forma prática e aplicável em ambientes reais de produção.
Quando uma aplicação cresce, o erro mais comum dos desenvolvedores é assumir que frameworks modernos como o Next.js resolvem a segurança de forma mágica. Não resolvem. Segurança é uma arquitetura coordenada em camadas de validação, persistência, controle de sessão, criptografia de dados e execução em tempo de execução.
1) SQL injection e autenticação insegura#
Vulnerabilidades de injeção SQL acontecem quando entradas fornecidas pelo usuário são interpoladas diretamente dentro da string de consulta do banco de dados, permitindo que atacantes manipulem a lógica da query.
Anti-pattern crítico (vulnerável)#
// /pages/api/login.js - VULNERÁVEL
const { email, password } = req.body;
// Interpolação direta e checagem de senha em texto plano (inseguro)
const sql = `SELECT * FROM users WHERE email = '${email}' AND password = '${password}'`;
const result = await db.query(sql);
Com um payload básico como ' OR 1=1 --, o atacante anula a checagem da senha e realiza login como o primeiro usuário retornado pelo banco (geralmente o administrador).
Correção definitiva: prepared statements + password hashing (bcrypt)#
Para corrigir essa falha de forma profissional, separamos a instrução SQL dos parâmetros de dados e validamos a senha usando hash seguro (com bcrypt ou argon2):
import bcrypt from 'bcrypt';
// 1. Prepared Statement: busca apenas pelo e-mail
const sql = "SELECT id, email, password_hash FROM users WHERE email = ?";
const result = await db.query(sql, [email]);
const user = result.rows[0];
// 2. Comparação segura do hash da senha
if (user && await bcrypt.compare(password, user.password_hash)) {
// 3. Prevenção de Session Fixation: regenera a sessão após login bem-sucedido
req.session.regenerate((err) => {
if (err) return next(err);
req.session.userId = user.id;
// 4. Log de auditoria estruturado (JSON format)
console.log(JSON.stringify({
event: 'LOGIN_SUCCESS',
userId: user.id,
ip: req.ip,
timestamp: new Date().toISOString()
}));
res.json({ success: true });
});
} else {
// Log estruturado de falha
console.log(JSON.stringify({
event: 'LOGIN_FAILURE',
email: email,
ip: req.ip,
timestamp: new Date().toISOString()
}));
res.status(401).json({ error: 'Credenciais inválidas' });
}
O perigo oculto em orms#
Mesmo utilizando ORMs consolidados como o Prisma, desenvolvedores frequentemente recorrem a métodos como $queryRawUnsafe para consultas complexas. Isso reabre a superfície de injeção. Em ambientes de produção:
- Priorize métodos nativos tipados do ORM (
findUnique,findFirst); - Caso precise de queries puras (raw), utilize tagged templates seguras (
$queryRawcom placeholders parametrizados); - Nunca concatene strings vindas de requisições de usuários nas queries.
2) Rate limiting em endpoints críticos#
Endpoints de autenticação expostos sem controle de taxa de requisições facilitam ataques de força bruta. Devemos limitar as tentativas de login por IP.
Implementação de rate limiting com express-rate-limit:#
const rateLimit = require('express-rate-limit');
export const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // Janela de 15 minutos
max: 5, // Limite de 5 tentativas por IP por janela
message: { error: 'Muitas tentativas de login. Tente novamente em 15 minutos.' },
standardHeaders: true, // Retorna dados do limite nos cabeçalhos RateLimit-*
legacyHeaders: false, // Desabilita cabeçalhos X-RateLimit-* legados
});
3) Proteção contra CSRF e validação de content-type#
Ataques de Cross-Site Request Forgery (CSRF) forçam o navegador do usuário autenticado a enviar requisições maliciosas para endpoints internos do SaaS.
Implementação de validação de CSRF token em API routes:#
import csurf from 'csurf';
const csrfProtection = csurf({ cookie: true });
export default async function handler(req, res) {
// Validar CSRF Token em requisições de mutação
if (['POST', 'PUT', 'DELETE'].includes(req.method)) {
const csrfToken = req.body._csrf || req.headers['x-csrf-token'];
if (!csrfToken || csrfToken !== req.session.csrfToken) {
return res.status(403).json({ error: 'CSRF token inválido ou ausente.' });
}
}
// Continuar processamento...
}
Validando o content-type header#
Outro ataque comum envolve o "Content-Type Sniffing", onde atacantes tentam enviar payloads maliciosos disfarçados de outros formatos. Validamos o cabeçalho explicitamente:
if (['POST', 'PUT'].includes(req.method)) {
const contentType = req.headers['content-type'];
if (!contentType || !contentType.includes('application/json')) {
return res.status(415).json({ error: 'Content-Type inválido. Apenas application/json é permitido.' });
}
}
4) XSS e sanitização na camada de execução#
Stored XSS (Cross-Site Scripting Persistido) em ambientes multi-tenant permite que dados maliciosos salvos por um usuário executem scripts no navegador de outros clientes ou de administradores.
Sanitização com dompurify#
Ao lidar com saídas de editores de texto rico (WYSIWYG), nunca injete o conteúdo diretamente sem sanitizar.
import DOMPurify from "dompurify";
// Sanitizar a entrada no frontend antes de renderizar
const safeContent = DOMPurify.sanitize(userInput);
return <div dangerouslySetInnerHTML={{ __html: safeContent }} />;
Regras operacionais para XSS:#
- Sanitização na Ingestão (Backend): Sempre limpe tags HTML proibidas e scripts antes de salvar no banco de dados;
- Sanitização na Renderização (Frontend): Utilize DOMPurify ao injetar strings HTML na DOM;
- Content Security Policy (CSP): Configure uma CSP rígida para restringir fontes de execução de scripts de terceiros.
5) Validação de inputs estrita com zod#
Toda entrada que entra na aplicação deve passar por validações de tipo, formato e tamanho máximo para evitar ataques de estouro de pilha (stack overflow) e Denial of Service (DoS).
Validação de esquema com limites de comprimento#
import { z } from 'zod';
export const userSchema = z.object({
name: z.string().min(3).max(100).trim(), // Limita tamanho máximo do input
email: z.string().email().max(255).toLowerCase(),
role: z.enum(["USER", "CLIENT"]).default("USER"),
}).strict(); // strict() impede parâmetros extras indesejados (Mass Assignment)
No endpoint do Next.js:
const parsed = userSchema.safeParse(req.body);
if (!parsed.success) {
return res.status(400).json(parsed.error.format());
}
6) HTTPS enforcement e cookies de sessão seguros#
A integridade do canal de transporte (HTTPS) é a linha base para evitar interceptações de tráfego (man-in-the-middle).
Redirecionamento para HTTPS em middleware do next.js:#
// middleware.js
export function middleware(request) {
const forwardedProto = request.headers.get('x-forwarded-proto');
const protocol = forwardedProto || request.nextUrl.protocol;
if (protocol !== 'https:' && process.env.NODE_ENV === 'production') {
const url = request.nextUrl.clone();
url.protocol = 'https:';
return Response.redirect(url);
}
}
Configurando cookies de sessão seguros (nextauth)#
Armazenar sessões ou JWTs no localStorage deixa os tokens expostos a roubos via XSS. Utilize cookies com diretivas estritas:
const cookies = {
sessionToken: {
name: `__Secure-next-auth.session-token`, // Prefixo __Secure- exige HTTPS
options: {
httpOnly: true, // Bloqueia leitura via document.cookie no JavaScript
sameSite: 'lax', // Mitigação de CSRF
path: '/',
secure: true, // Transmissão apenas via HTTPS
},
},
};
7) Tratamento seguro de erros e headers no next.config.js#
Expor logs de banco de dados ou detalhes internos do servidor em mensagens de erro do cliente facilita a descoberta de vulnerabilidades por atacantes.
Tratamento seguro de exceções#
try {
const result = await db.query(sql, params);
res.json(result);
} catch (error) {
// 1. Log interno detalhado para o time de SRE/DevSecOps
console.error('Database query failure:', error);
// 2. Resposta genérica e segura para o usuário externo
res.status(500).json({ error: 'Erro interno do servidor.' });
}
Cabeçalhos de segurança no next.config.js#
Adicione cabeçalhos HTTP de defesa diretamente na configuração do Next.js:
// next.config.js
module.exports = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'X-XSS-Protection', value: '1; mode=block' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{ key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
{
key: 'Content-Security-Policy',
value: "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';"
},
],
},
];
},
};
8) Ferramentas de auditoria e teste de segurança#
Automatize testes de segurança e validações de vulnerabilidades no seu pipeline de CI/CD:
# 1. Scanner de segurança automático OWASP ZAP (análise de baseline)
docker run -t ghcr.io/zaproxy/zaproxy zap-baseline.py -t https://seu-app.com
# 2. Verificar vulnerabilidades conhecidas em dependências do npm
npm audit
npm audit fix
# 3. Análise estática de código com foco em segurança (Snyk)
npx snyk test
# 4. ESLint com regras específicas para segurança em JavaScript/TypeScript
npm install eslint-plugin-security --save-dev
Tabela de referência: OWASP top 10 vs next.js#
| OWASP | Vulnerabilidade | Prevenção no Next.js |
|---|---|---|
| A01 | Broken Access Control | Middleware global + RBAC estrito |
| A02 | Cryptographic Failures | Criptografia bcrypt/argon2 + HTTPS com HSTS |
| A03 | Injection | Prepared Statements + Validação Zod + $queryRaw seguro |
| A04 | Insecure Design | Modelagem de ameaças (Threat Modeling) no ciclo ágil |
| A05 | Security Misconfiguration | Cabeçalhos HTTP no next.config.js + CSP rígida |
| A06 | Vulnerable Components | Auditoria sistemática via npm audit e Snyk |
| A07 | Auth Failures | Multi-factor auth + Rate Limiting + Regeneração de sessões |
| A08 | Data Integrity | Validação estruturada de inputs + Subresource Integrity |
| A09 | Logging Failures | Logs formatados em JSON + Centralização em SIEM |
| A10 | SSRF | Validação e whitelist de requisições externas em SSR |
Runbook: auditoria de segurança next.js#
Siga este roteiro estruturado para auditar a segurança de suas aplicações baseadas em Next.js:
1. Configuração e dependências#
- [ ] Verificar
next.config.js(presença de headers HTTP essenciais e regras de CSP). - [ ] Analisar
.envpara garantir que segredos e chaves de API privada não foram commitados no repositório. - [ ] Validar dependências rodando
npm auditou Snyk no repositório.
2. Autenticação e controle de sessão#
- [ ] Garantir que o hash de senhas utiliza algoritmos robustos (bcrypt com cost 12+ ou argon2id).
- [ ] Validar se as credenciais da sessão são armazenadas em cookies com flags
HttpOnly,SecureeSameSite. - [ ] Certificar a existência de regeneração do ID de sessão após logins para mitigar Session Fixation.
- [ ] Testar a existência de rate limiting ativo em endpoints de autenticação e reset de senhas.
3. Entrada e sanitização de dados#
- [ ] Verificar se todas as rotas de API possuem esquemas Zod com comprimento de strings limitado (
max()) e validados na borda. - [ ] Localizar ocorrências de
dangerouslySetInnerHTMLe certificar o uso de DOMPurify para sanitização de saída. - [ ] Garantir que todas as consultas ao banco utilizam drivers parametrizados (Prepared Statements).
4. Resposta e monitoramento#
- [ ] Testar se mensagens de erro de infraestrutura ou do banco de dados não são transmitidas ao cliente em produção.
- [ ] Garantir que tentativas de autenticação falhas e erros críticos geram logs estruturados (JSON) contendo IPs e timestamps.
Considerações práticas#
Garantir a integridade de uma aplicação SaaS baseada em Next.js exige atenção constante à fronteira de inputs, armazenamento de chaves de sessão e tratamento robusto de dados no banco. Aplicar práticas de menor privilégio e defesa em profundidade mitiga os riscos antes que eles se transformem em incidentes reais de segurança.
Este artigo foi útil?
Deixe uma reação rápida para apoiar o conteúdo:
Este post está licenciado sob CC BY-NC.



Comentários
Participe da discussão abaixo.
0 comentários