Voltar para blog

Segurança Ofensiva na Prática: XSS, SQL Injection e Arquitetura de Confiança Zero em Next.js

08/03/2026 · 4 min · Cibersegurança

Compartilhar

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:

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:

---

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:

  1. na entrada (backend): sanitização/normalização e política de campos permitidos;
  2. 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:

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:

---

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

---

5) Arquitetura de confiança zero aplicada ao SaaS

Confiança zero em aplicação web significa não confiar em:

CamadaPrática recomendada
InputValidação backend com Zod + .strict()
PersistênciaPrepared statements + menor privilégio no banco
SessãoCookies HttpOnly/Secure + rotação de tokens
ExecuçãoCSP rígida + sanitização com DOMPurify
ObservabilidadeLogs 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:

  1. Endpoints de login e busca: procurar concatenação SQL, raw queries e filtros frágeis.
  2. Pontos de render HTML: localizar dangerouslySetInnerHTML e validar sanitização.
  3. Sessão/token: confirmar onde credencial vive e como é renovada.
  4. Schemas: validar se toda rota mutável passa por schema estrito.
  5. Headers de segurança: CSP, X-Content-Type-Options, Referrer-Policy, Frame-Options.
  6. 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.

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.

CC BY-NC

Este post está licenciado sob CC BY-NC.

Comentários

Participe da discussão abaixo.