Skip to main content

Como prevenir ataques de injeção de SQL no Node.js

Escrito por
Headshot of Lucien Chemaly

Lucien Chemaly

20 de fevereiro de 2024

0 minutos de leitura

À medida que aumenta a dependência de sistemas de software, também surgem novas ameaças à segurança. Uma ameaça importante para desenvolvedores, especialmente para quem trabalha com Node.js, é a injeção de SQL.

A injeção de SQL é um ataque malicioso em que código SQL nocivo é inserido em um sistema, expondo informações confidenciais, corrompendo ou excluindo dados e, às vezes, concedendo acesso não autorizado a invasores. Combater essa ameaça é difícil e reforça a necessidade de proteger suas aplicações Node.js.

Neste artigo, você vai entender por que os ataques de injeção de SQL representam uma ameaça significativa e como proteger suas aplicações Node.js contra eles.

Entenda a injeção de SQL em aplicações Node.js

Os ataques de injeção de SQL ocorrem principalmente quando uma aplicação Node.js não trata corretamente as entradas do usuário. Quando essas entradas são usadas em uma consulta SQL sem validação, sanitização ou construção adequada da consulta, abre-se uma brecha para que invasores manipulem a consulta, obtendo acesso não autorizado ou vazando dados. Para conhecer as vulnerabilidades antigas e recentes do Node.js, consulte a lista na Snyk Vulnerability Database.

Aplicação Node.js com Express vulnerável à injeção de SQL

Para entender melhor como funciona a injeção de SQL, vamos criar rapidamente uma aplicação vulnerável usando Node.js, Express e um banco de dados PostgreSQL. A aplicação recebe dados de um formulário, monta uma consulta SQL e a executa no banco de dados para buscar informações.

Comece baixando e instalando o Node.js na sua máquina. O Node.js é o ambiente de execução que executa seu código JavaScript no servidor.

Em seguida, crie um diretório para o projeto (por exemplo, vulnerable-app) e acesse esse diretório no terminal com o comando cd vulnerable-app.

No diretório do projeto, execute o comando npm init -y. Ele cria um arquivo package.json com valores padrão, que funciona como manifesto do projeto Node.js.

Instale o Express e a biblioteca pg com o comando npm install express pg. O Express é um framework leve para aplicações web, e pg é um cliente Node.js para PostgreSQL.

Em seguida, baixe e instale o PostgreSQL. Depois da instalação, crie um banco de dados chamado vulnerable_db. Nesse banco, crie uma tabela chamada users com uma coluna chamada name. Use o seguinte comando SQL:

   CREATE TABLE users (
       id SERIAL PRIMARY KEY,
       name VARCHAR(255)
   );

Para obter mais informações sobre como instalar e executar o PostgreSQL, consulte esta documentação da W3Schools.

Crie um arquivo chamado app.js e adicione o seguinte código:

const express = require("express");
const { Client } = require("pg");
const app = express();
app.use(express.urlencoded({ extended: true }));
app.use(express.json()); // To handle JSON input
const client = new Client({
  host: "your-host",
  database: "your-database",
  username: "your-username",
  password: "your-password",
  port: "your-port",
});
client.connect();
app.post("/search", (req, res) => {
  const userInput = req.body.userInput;
  const query = "SELECT * FROM users WHERE name = '" + userInput + "';";
  client.query(query, (err, result) => {
    if (err) throw err;
    res.send(result.rows);
  });
});
app.post("/create", (req, res) => {
  const { name } = req.body;
  if (!name) {
    res.status(400).send("Name is required");
    return;
  }
  const query = "INSERT INTO users (name) VALUES ($1) RETURNING *;";
  client.query(query, [name], (err, result) => {
    if (err) {
      console.error(err);
      res.status(500).send("Internal Server Error");
      return;
    }
    res.send(result.rows[0]); // Respond with the created user record
  });
});
app.listen(3000, () => {
  console.log("Server is running on port 3000");
});

Este código inicializa um servidor Express e configura conexões com um banco de dados PostgreSQL usando a biblioteca pg. O servidor é configurado para processar entradas codificadas em URL e em JSON.

São definidas duas rotas: POST /search e POST /create. A rota /search recebe dados do usuário no corpo da requisição, monta uma consulta SQL e a executa no banco de dados, retornando o resultado. No entanto, ela tem uma vulnerabilidade de injeção de SQL, pois concatena a entrada do usuário à consulta. A rota /create extrai um valor name do corpo da requisição, verifica se ele foi informado e, então, monta uma consulta SQL parametrizada e segura para inserir um novo usuário no banco de dados, retornando o registro criado. Por fim, o servidor começa a escutar na porta 3000 e exibe uma mensagem no console para indicar que está pronto.

Observação: Substitua your-username, your-password, your-database, your-host e your-port pelas configurações que você definiu para o PostgreSQL.

Como não há validação nem sanitização das entradas, o código é vulnerável à injeção de SQL. Um invasor pode inserir no formulário um valor como ' OR '1'='1'; --. Essa entrada encerra a consulta original e a modifica para SELECT * FROM users WHERE name = '' OR '1'='1'; --';, que retorna todos os registros da tabela users, pois '1'='1' é sempre verdadeiro. Este é um exemplo clássico de injeção de SQL por concatenação de strings.

Agora que está tudo configurado, é hora de iniciar o servidor. Execute o comando node app.js no terminal. Isso inicia o servidor, e você deverá ver a mensagem Server is running on port 3000, indicando que ele está funcionando corretamente:

Terminal mostrando um aplicativo Node.js iniciado com node app.js e a mensagem “Server is running on port 3000.”

Para testar a aplicação, crie alguns usuários pelo terminal ou shell:

curl -X POST http://localhost:3000/create -H "Content-Type: application/json" -d '{"name":"User1"}'

curl -X POST http://localhost:3000/create -H "Content-Type: application/json" -d '{"name":"User2"}'

curl -X POST http://localhost:3000/create -H "Content-Type: application/json" -d '{"name":"User3"}'

curl -X POST http://localhost:3000/create -H "Content-Type: application/json" -d '{"name":"User4"}'

curl -X POST http://localhost:3000/create -H "Content-Type: application/json" -d '{"name":"User5"}'

Em seguida, se quiser buscar um usuário específico para verificar sua identidade, atualizar dados ou realizar auditorias e monitoramento, envie uma solicitação de busca dos dados do usuário. Execute o comando a seguir:

curl -X POST http://localhost:3000/search -H "Content-Type: application/json" -d '{"userInput":"User1"}'

A saída deverá ser semelhante a esta:

[{"id":1,"name":"User1"}]

Agora, injete SQL para obter todos os usuários usando o seguinte comando:

curl -X POST http://localhost:3000/search -H "Content-Type: application/json" -d "{\"userInput\":\"' OR '1'='1'; -- \"}"

A saída deverá ser semelhante a esta:

[{"id":1,"name":"User1"},{"id":2,"name":"User2"},{"id":3,"name":"User3"},{"id":4,"name":"User4"},{"id":5,"name":"User5"},{"id":6,"name":"User2"},{"id":7,"name":"User2"},{"id":8,"name":"User3"},{"id":9,"name":"User4"},{"id":10,"name":"User5"}]

Neste comando, os dados JSON ficam entre aspas duplas externas (" ") para formar uma string JSON completa, enquanto as aspas internas dos dados JSON são escapadas com uma barra invertida (\) para que sejam incluídas corretamente na string. O valor de userInput é definido como ' OR '1'='1'; -- e, como explicado anteriormente, manipula a consulta SQL para retornar todos os registros de usuários, alterando a condição para que seja sempre verdadeira.

Como prevenir vulnerabilidades de injeção de SQL no Node.js

Agora que você sabe como é fácil ocorrerem ataques de injeção de SQL, veja algumas maneiras de proteger sua aplicação e garantir que ela seja segura para seus usuários.

Use marcadores de posição nas consultas

Evitar montar consultas SQL por concatenação de strings é essencial para prevenir a injeção de SQL. Invasores podem manipular consultas ao inserir código SQL malicioso nos campos de entrada. Em vez disso, use marcadores de posição para garantir que as entradas do usuário sejam tratadas como dados, e não como código executável:

app.post('/search', (req, res) => {
    const userInput = req.body.userInput;
    const query = "SELECT * FROM users WHERE name = $1;";
    client.query(query, [userInput], (err, result) => {
        if (err) throw err;
        res.send(result.rows);
    });
});

Use consultas preparadas

Uma das maneiras mais eficazes de prevenir ataques de injeção de SQL é usar consultas preparadas. Elas garantem que as entradas do usuário sejam sempre tratadas como dados, e não como código executável.

Veja como modificar a rota /search para usar consultas preparadas com a biblioteca pg:

app.post('/search', (req, res) => {
    const userInput = req.body.userInput;
    const query = "SELECT * FROM users WHERE name = $1;";
    const preparedQuery = {
        text: query,
        values: [userInput]
    };
    client.query(preparedQuery, (err, result) => {
        if (err) throw err;
        res.send(result.rows);
    });
});

Neste código, a consulta preparada é definida como um objeto com a propriedade text para a consulta SQL e a propriedade values para a entrada do usuário. Essa separação permite definir o modelo da consulta SQL uma única vez e fornecer valores diferentes para o marcador de posição a cada execução. Assim, as entradas do usuário são tratadas com mais segurança e eficiência nas consultas SQL.

Valide e sanitize as entradas do usuário

Verificar as entradas do usuário é essencial para garantir que elas atendam a requisitos específicos, como formato correto, comprimento ou intervalo de valores. Bibliotecas de validação como joi ou express-validator simplificam esse processo. Elas oferecem regras e funções de validação predefinidas, facilitando a aplicação dos requisitos e a automação das verificações.

Além disso, sanitizar as entradas para remover ou escapar caracteres potencialmente nocivos é essencial para manter um ambiente de aplicação seguro e robusto. A sanitização de entradas consiste em limpar os dados fornecidos pelos usuários para evitar danos ou comportamentos inesperados durante o processamento pela aplicação.

Veja como implementar a validação e a sanitização usando a biblioteca express-validator:

 const { body, validationResult } = require('express-validator');

app.post("/search", [
  body('userInput').trim().escape().isAlphanumeric(),
], (req, res) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(400).json({ errors: errors.array() });
  }

  const userInput = req.body.userInput;
  const query = "SELECT * FROM users WHERE name = $1;";

  client.query(query, [userInput], (err, result) => {
      if (err) throw err;
      res.send(result.rows);
  });
});

Use as versões mais recentes de bibliotecas ORM e SQL

Use sempre as versões mais recentes de bibliotecas ORM e SQL. Os mantenedores dessas bibliotecas lançam atualizações e correções com frequência para resolver vulnerabilidades de segurança conhecidas:

npm install pg@latest

Revise o código regularmente e realize auditorias de segurança

Faça revisões de código e auditorias de segurança regulares para identificar vulnerabilidades o quanto antes. Use ferramentas como npm audit ou snyk para automatizar a identificação de vulnerabilidades conhecidas nas suas dependências.

Detecte vulnerabilidades com a extensão Snyk IDE para Visual Studio Code

Snyk é uma ferramenta de segurança criada para desenvolvedores que ajuda a identificar e corrigir vulnerabilidades no código e em dependências de código aberto. Ela se integra perfeitamente ao fluxo de desenvolvimento, monitorando e analisando continuamente falhas de segurança, inclusive as relacionadas à injeção de SQL.

Além de identificar vulnerabilidades, a Snyk também cria pull requests com correções automatizadas, facilitando a resolução e a prevenção de possíveis problemas de segurança. A plataforma se integra a várias ferramentas de desenvolvimento, como GitHub, GitLab e Bitbucket, e oferece uma extensão dedicada para o Visual Studio Code, com feedback em tempo real durante a programação.

Configure uma extensão da Snyk

É fácil configurar uma extensão da Snyk. Comece baixando e instalando o Visual Studio Code, caso ainda não tenha feito isso. Depois, abra o Visual Studio Code e acesse a visualização Extensions pressionando Ctrl + Shift + X no Windows ou Command + Shift + X no macOS.

Na visualização Extensions, digite "Snyk" na barra de pesquisa e clique no botão Install da extensão Snyk:

Marketplace do Visual Studio Code mostrando uma busca por “snyk” e a extensão Snyk Security com botões de instalação.

Após a instalação, você verá a extensão Snyk na barra lateral esquerda do Visual Studio Code. Clique nela e, em seguida, em Trust workspace and connect. Para usar a extensão, entre na sua conta Snyk. Se ainda não tiver uma conta, você pode criar uma gratuitamente:

Visual Studio Code exibindo a extensão do Snyk, com o botão “Confiar no espaço de trabalho e conectar” em destaque e detalhes do scanner de vulnerabilidades.

Em seguida, será solicitado que você entre na conta e faça a autenticação. Depois, deverá aparecer a mensagem Authenticated:

Página de confirmação de autenticação da Snyk com a mensagem “Autenticado” e “Sua conta foi autenticada. A Snyk já está pronta para uso.”

Depois de instalada e configurada, a extensão Snyk analisa automaticamente seu projeto em busca de vulnerabilidades conhecidas, incluindo possíveis riscos de injeção de SQL.

Acesse sua conta na plataforma Snyk e siga as instruções para ativar o Snyk Code. Ativar o Snyk Code é importante para que a Snyk detecte vulnerabilidades no código, como injeções de SQL.

Se a opção Snyk Code não estiver ativada na sua conta Snyk, a extensão Snyk para Visual Studio Code não detectará injeções de SQL relacionadas a más práticas no código. Ela detectará apenas vulnerabilidades relacionadas a bibliotecas desatualizadas e similares.

A Snyk não se limita a identificar vulnerabilidades: ela também oferece sugestões e referências para corrigir os problemas. Esse recurso pode ser muito útil, especialmente ao trabalhar com bases de código grandes ou prazos apertados.

Todo o código deste tutorial está disponível no repositório do GitHub a seguir.

Conclusão

Neste artigo, você aprendeu como a injeção de SQL ocorre em aplicações Node.js e conheceu várias estratégias para preveni-la. De atualizar bibliotecas ORM e SQL e sanitizar entradas de usuários a usar marcadores de posição nas consultas e aproveitar a extensão da Snyk para Visual Studio Code, você tem várias maneiras de proteger suas aplicações Node.js contra ataques de injeção de SQL.

A extensão Snyk não só ajuda a identificar vulnerabilidades de injeção de SQL, como também ensina a resolvê-las, funcionando como uma mentora virtual de segurança. Ao integrar a Snyk ao seu fluxo de desenvolvimento, você deixa de apenas reagir às ameaças e passa a preveni-las, mantendo suas aplicações robustas e seguras antes de chegarem à produção.

À medida que você avança no desenvolvimento com Node.js, lembre-se de que segurança não é uma tarefa pontual, mas um processo contínuo. Mantenha-se atualizado sobre as práticas recomendadas de segurança mais recentes, continue aprendendo e mantenha suas aplicações seguras. Seus usuários agradecem, e você fica mais tranquilo sabendo que seus dados e os deles continuam protegidos.

Comece a resolver desafios de Capture the Flag

Aprenda a resolver desafios de Capture the Flag assistindo ao nosso workshop virtual introdutório sob demanda.

Leia mais

blog feature ai lilac
Blog

Transforme seu backlog de vulnerabilidades em pull requests com remediação agentiva

O Evo Remediation Agent está em Open Preview e traz a resolução automatizada de dependências para o Evo Agentic AppSec. Agora, as equipes de AppSec podem executar campanhas contínuas de remediação

AI Background min crop
Blog

A IA está acionando todos os riscos que você aceitou: sete insights do painel de segurança de IA sem filtros

A IA está tornando os ataques mais baratos e rápidos, expondo riscos que as organizações já carregam. Quatro líderes de segurança explicam por que a defesa na velocidade das máquinas e a correção mais rápida são essenciais.

blog feature ai violet
Blog

VulnBump: agentes de IA conseguem corrigir dependências vulneráveis sem quebrar o build?

O VulnBump testa agentes de IA em 307 atualizações reais de dependências. Uma correção só é considerada válida se eliminar a vulnerabilidade e mantiver o build e os testes passando.