Segurança Ofensiva na Prática: XSS, SQL Injection e Arquitetura de Confiança Zero em Next.js
Este artigo consolida falhas críticas que identifiquei em auditorias de aplicações SaaS. O risco aqui não é só técnico, é financeiro, reputacional e jurídico. O objetivo é documentar engenharia de segurança aplicada em ambiente real, independente de framework.
Quando a aplicação cresce, o erro comum é assumir que “React/Next resolve segurança”. Não resolve. Segurança é arquitetura e disciplina operacional: validação, persistência, sessão e execução precisam funcionar como camadas coordenadas.
---
1) SQL Injection: o erro nasce na camada de persistência
Framework moderno não impede decisão ruim em acesso a dados. SQL Injection aparece quando input externo é interpolado dentro do comando SQL.
Anti-pattern real
// /pages/api/login.js - VULNERÁVEL
const { email, password } = req.body;
const sql = `SELECT * FROM users WHERE email = '${email}' AND password = '${password}'`;
const result = await db.query(sql);
Com payload simples como ' OR 1=1 --, o atacante ignora o filtro de senha e pode escalar sessão.
Correção definitiva: Prepared Statements
const sql = "SELECT * FROM users WHERE email = ? AND password = ?";
const result = await db.query(sql, [email, password]);
Aqui o comando e os dados trafegam separados. O banco interpreta o input como valor, não como instrução.
O perigo oculto nos ORMs
Mesmo com Prisma, ainda vejo equipe usando $queryRawUnsafe em “casos especiais”. É exatamente ali que a superfície reabre. Em cenário profissional:
- priorize métodos tipados (
findUnique,findFirst,update); - quando precisar raw query, use tagged templates seguras (
$queryRawcom placeholders); - nunca aceite string montada com concatenação vinda de request.
Contenção adicional na camada de banco
Além do código, aplico política de menor privilégio no usuário SQL da aplicação:
- app de autenticação não precisa
DROP,ALTER,GRANT; - separar usuário de leitura e escrita quando viável;
- habilitar logs de query suspeita para investigação pós-incidente.
---
2) XSS em SaaS: quando comentário vira sequestro de sessão
Em ambiente multi-tenant, um Stored XSS permite um cliente atacar outro cliente ou atingir sessão administrativa no painel interno.
Gatilho clássico: dangerouslySetInnerHTML
Se você renderiza HTML de editor rico sem sanitização, está entregando execução arbitrária no browser.
import DOMPurify from "dompurify";
const safeContent = DOMPurify.sanitize(userInput);
<div dangerouslySetInnerHTML={{ __html: safeContent }} />
Ponto operacional importante
Sanitizar no front ajuda, mas não é suficiente sozinho. Eu trato em dois pontos:
- na entrada (backend): sanitização/normalização e política de campos permitidos;
- na saída (frontend): renderização defensiva para qualquer conteúdo persistido.
Assim, se uma camada falhar, a outra segura.
Defesa em profundidade com CSP
Mesmo que um XSS passe, CSP bem configurada reduz impacto de exfiltração.
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.scripts.com;
Em produção, recomendo começar com Content-Security-Policy-Report-Only para mapear quebra legítima, depois aplicar política bloqueante.
---
3) Gestão de tokens: por que localStorage é dívida de segurança
Guardar JWT em localStorage é um risco direto: qualquer XSS recupera token e sequestra conta.
Baseline segura de sessão
Cookies de sessão com:
- HttpOnly: JavaScript não lê o cookie.
- Secure: envio apenas em HTTPS.
- SameSite=Strict (ou Lax conforme fluxo): mitigação de CSRF.
Erro recorrente com NextAuth.js
Expor accessToken bruto no objeto session do client. Isso amplia a superfície para script injetado e extensão maliciosa.
Prática madura:
- token sensível fica no servidor;
- client recebe só metadados mínimos (nome, avatar, expiração de sessão);
- rotação de token com janela curta e invalidação ativa em logout e suspeita de abuso.
---
4) Validação como contrato de segurança com Zod
Validação visual no frontend melhora UX. Validação no backend protege o sistema.
Schema centralizado e estrito
export const userSchema = z.object({
name: z.string().min(3).trim(),
email: z.string().email().toLowerCase(),
role: z.enum(["USER", "CLIENT"]).default("USER"),
}).strict();
strict() impede injeção de campos extras (Mass Assignment), por exemplo tentar enviar role: "ADMIN" fora do fluxo.
Primeira linha de defesa no endpoint
const parsed = userSchema.safeParse(req.body);
if (!parsed.success) return res.status(400).json(parsed.error.format());
Regra de ouro
- validar sempre no servidor, mesmo se formulário já valida no client;
- tipar e centralizar schema para evitar drift entre times;
- negar por padrão qualquer campo desconhecido.
---
5) Arquitetura de confiança zero aplicada ao SaaS
Confiança zero em aplicação web significa não confiar em:
- input de usuário autenticado;
- dados vindos de API interna;
- metadado de sessão no client;
- “sanitização” feita por outro serviço sem verificação local.
| Camada | Prática recomendada |
|---|---|
| Input | Validação backend com Zod + .strict() |
| Persistência | Prepared statements + menor privilégio no banco |
| Sessão | Cookies HttpOnly/Secure + rotação de tokens |
| Execução | CSP rígida + sanitização com DOMPurify |
| Observabilidade | Logs de auth, anomalias de payload e correlação por request-id |
---
6) Runbook rápido de auditoria ofensiva (campo)
Quando entro em uma revisão de segurança em Next.js, sigo esta ordem:
- Endpoints de login e busca: procurar concatenação SQL, raw queries e filtros frágeis.
- Pontos de render HTML: localizar
dangerouslySetInnerHTMLe validar sanitização. - Sessão/token: confirmar onde credencial vive e como é renovada.
- Schemas: validar se toda rota mutável passa por schema estrito.
- Headers de segurança: CSP,
X-Content-Type-Options,Referrer-Policy,Frame-Options. - Teste de abuso: payload controlado de SQLi/XSS em ambiente de homologação.
Isso reduz auditoria superficial e aumenta taxa de descoberta real.
---
Conclusão técnica
Segurança não é “feature final”, é fundamento estrutural.
- React/Next.js não geram XSS sozinhos; quem gera é confiança cega em input.
- ORM não salva de SQL Injection se você força query insegura.
- Token no client sem critério é sessão pronta para sequestro.
Validação é contrato. Persistência é fronteira. Sessão é ativo crítico.
Quem ignora esses fundamentos em 2026 aceita um risco técnico e jurídico que não deveria entrar em produção.
Este post está licenciado sob CC BY-NC.
Comentários
Participe da discussão abaixo.
Comentários ainda não configurados. Adicione as opções do Cusdis em /assets/json/config/blog-comments-config.json.