Como Usar Segurança em Node.js: Injeção, SSRF, Path Traversal e Hardening em Produção Já leu

Injeção em Node.js A injeção é uma das vulnerabilidades mais críticas em aplicações web. Em Node.js, ocorre quando dados não validados são interpretados como código ou comando pelo servidor. A forma mais comum é a injeção SQL, mas também temos injeção de NoSQL, OS command injection e template injection. SQL Injection acontece quando concatenamos entrada do usuário diretamente em queries. No exemplo abaixo, um atacante pode manipular a lógica da consulta: SELECT FROM users WHERE id = ${req.params.id} A defesa é usar prepared statements (queries parametrizadas), que separam código de dados: NoSQL Injection é igualmente perigosa. Em MongoDB, objetos JavaScript são interpretados como filtros: Sempre valide e sanitize inputs usando bibliotecas como , ou . SSRF (Server-Side Request Forgery) e Path Traversal SSRF ocorre quando sua aplicação faz requisições HTTP para URLs controladas pelo usuário, permitindo acesso a recursos internos. Um atacante pode acessar localhost, metadados AWS ou serviços internos: Path Traversal permite acesso a arquivos fora do diretório permitido

Injeção em Node.js

A injeção é uma das vulnerabilidades mais críticas em aplicações web. Em Node.js, ocorre quando dados não validados são interpretados como código ou comando pelo servidor. A forma mais comum é a injeção SQL, mas também temos injeção de NoSQL, OS command injection e template injection.

SQL Injection acontece quando concatenamos entrada do usuário diretamente em queries. No exemplo abaixo, um atacante pode manipular a lógica da consulta:

// ❌ VULNERÁVEL
const express = require('express');
const mysql = require('mysql');
const app = express();

app.get('/user/:id', (req, res) => {
  const query = `SELECT * FROM users WHERE id = ${req.params.id}`;
  // Entrada: 1 OR 1=1 -- causa retorno de todos os usuários
  connection.query(query, (err, results) => {
    res.json(results);
  });
});

A defesa é usar prepared statements (queries parametrizadas), que separam código de dados:

// ✅ SEGURO
app.get('/user/:id', (req, res) => {
  const query = 'SELECT * FROM users WHERE id = ?';
  connection.query(query, [req.params.id], (err, results) => {
    res.json(results);
  });
});

NoSQL Injection é igualmente perigosa. Em MongoDB, objetos JavaScript são interpretados como filtros:

// ❌ VULNERÁVEL
app.post('/login', (req, res) => {
  const user = await db.collection('users').findOne({
    email: req.body.email,
    password: req.body.password
  });
  // Entrada: {"$ne": ""} contorna autenticação
});

// ✅ SEGURO
app.post('/login', (req, res) => {
  const user = await db.collection('users').findOne({
    email: String(req.body.email),
    password: String(req.body.password)
  });
});

Sempre valide e sanitize inputs usando bibliotecas como joi, yup ou validator.js.

SSRF (Server-Side Request Forgery) e Path Traversal

SSRF ocorre quando sua aplicação faz requisições HTTP para URLs controladas pelo usuário, permitindo acesso a recursos internos. Um atacante pode acessar localhost, metadados AWS ou serviços internos:

// ❌ VULNERÁVEL
const axios = require('axios');

app.post('/fetch-url', (req, res) => {
  axios.get(req.body.url).then(response => {
    res.json(response.data);
  });
  // URL: http://localhost:6379 (Redis)
  // URL: http://169.254.169.254/latest/meta-data (AWS)
});

// ✅ SEGURO
const url = require('url');
const axios = require('axios');

app.post('/fetch-url', (req, res) => {
  try {
    const parsedUrl = new URL(req.body.url);

    // Whitelist de domínios permitidos
    const whitelist = ['api.example.com', 'cdn.example.com'];
    if (!whitelist.includes(parsedUrl.hostname)) {
      return res.status(403).json({ error: 'Domain not allowed' });
    }

    // Bloqueie IPs privados
    if (/^(localhost|127\.|10\.|172\.|192\.168\.)/.test(parsedUrl.hostname)) {
      return res.status(403).json({ error: 'Private network access forbidden' });
    }

    axios.get(req.body.url).then(response => {
      res.json(response.data);
    });
  } catch (err) {
    res.status(400).json({ error: 'Invalid URL' });
  }
});

Path Traversal permite acesso a arquivos fora do diretório permitido usando ../. Um atacante pode ler /etc/passwd ou arquivos privados:

// ❌ VULNERÁVEL
const fs = require('fs');
const path = require('path');

app.get('/file/:name', (req, res) => {
  const filePath = `/uploads/${req.params.name}`;
  // Entrada: ../../etc/passwd lê arquivo do sistema
  fs.readFile(filePath, (err, data) => {
    res.send(data);
  });
});

// ✅ SEGURO
app.get('/file/:name', (req, res) => {
  const baseDir = path.resolve('/uploads');
  const filePath = path.resolve(path.join(baseDir, req.params.name));

  // Valide se o arquivo resolvido está dentro do diretório base
  if (!filePath.startsWith(baseDir)) {
    return res.status(403).json({ error: 'Access denied' });
  }

  fs.readFile(filePath, (err, data) => {
    if (err) return res.status(404).json({ error: 'File not found' });
    res.send(data);
  });
});

Use path.resolve() para normalizar caminhos e sempre valide se o resultado está dentro do diretório esperado.

Hardening e Boas Práticas

Hardening significa endurecer sua aplicação contra múltiplos vetores de ataque. Comece com validação robusta de inputs, uso de variáveis de ambiente para secrets, e headers de segurança HTTP:

const express = require('express');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const validator = require('validator');

const app = express();

// Helmet adiciona headers de segurança (CSP, X-Frame-Options, etc)
app.use(helmet());

// Rate limiting previne força bruta
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutos
  max: 100 // máximo 100 requisições
});
app.use('/api/', limiter);

// Validação rigorosa
app.post('/register', (req, res) => {
  const { email, password } = req.body;

  if (!validator.isEmail(email)) {
    return res.status(400).json({ error: 'Invalid email' });
  }

  if (!validator.isLength(password, { min: 12 })) {
    return res.status(400).json({ error: 'Password too weak' });
  }

  // Hash de senha com bcrypt
  const bcrypt = require('bcrypt');
  const hashedPassword = bcrypt.hashSync(password, 10);

  // Salve no banco...
  res.json({ success: true });
});

// CORS restritivo
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', process.env.ALLOWED_ORIGINS);
  res.header('Access-Control-Allow-Credentials', 'true');
  next();
});

Práticas adicionais essenciais: nunca exponha stack traces em produção, use logs estruturados, mantenha dependências atualizadas com npm audit, implemente autenticação forte (JWT com expiração curta), use HTTPS obrigatório, e considere WAF (Web Application Firewall) para ambientes críticos.

Conclusão

Os três pilares da segurança em Node.js são: validação rigorosa (sempre desconfie de inputs), separação de código e dados (prepared statements, whitelist), e hardening sistemático (headers, rate limiting, HTTPS). Vulnerabilidades como injeção, SSRF e path traversal continuam entre as mais exploradas porque desenvolvedores negligenciam essas fundações. Implemente essas práticas desde o design, não como patch posterior — segurança é construída, não adicionada.

Referências


Artigos relacionados