Prevenção de Injeção SQL: o Guia do Desenvolvedor (2026)

Web Security
14 min de leitura
Prevenção de Injeção SQL: o Guia do Desenvolvedor (2026)
Nesta página
  1. O que é prevenção de injeção SQL?
  2. Por que a injeção SQL acontece: a causa raiz
  3. Consultas parametrizadas: a defesa principal
    1. PHP (PDO)
    2. Python (psycopg2 / sqlite3)
    3. Java (PreparedStatement)
    4. Node.js (mysql2 / pg)
  4. Validação de entrada e lista de permissão
  5. Menor privilégio e defesa em profundidade
  6. Como testar suas defesas contra injeção SQL
  7. Lista de verificação para prevenção de injeção SQL
  8. Considerações legais e éticas
  9. Perguntas frequentes
  10. Seus próximos passos

A prevenção de injeção SQL se resume a um único hábito: nunca construir uma consulta colando a entrada do usuário dentro de uma string. O banco de dados deve tratar o que o usuário digita como dados, nunca como parte do comando. Acerte isso e toda essa classe de ataque praticamente desaparece. A forma mais rápida de acreditar nisso é quebrar você mesmo uma consulta vulnerável primeiro, então abra o lab Query Quake da HackerDNA e veja uma única aspa reescrever um login antes de continuar a leitura.

A injeção SQL está na categoria de injeção do OWASP Top 10 desde a publicação da primeira lista, e ainda está custando vazamentos de dados às empresas em 2026. Este guia é a metade defensiva da história. Se você quiser primeiro ver como o ataque funciona do outro lado, nosso tutorial de injeção SQL percorre o caminho ofensivo. Aqui cobrimos consultas parametrizadas, validação de entrada, menor privilégio e como testar de verdade se suas defesas resistem.

Resumo: a injeção SQL acontece quando uma aplicação mistura a entrada do usuário em uma string de consulta, de modo que a entrada pode mudar o que a consulta faz. A correção principal são as consultas parametrizadas (instruções preparadas), que enviam o SQL e os dados por canais separados, para que a entrada nunca seja interpretada como código. Reforce isso com validação por lista de permissão para o que não dá para parametrizar, uma conta de banco de dados de menor privilégio e um tratamento seguro de erros. O escape é o último recurso, não uma estratégia.

O que é prevenção de injeção SQL?

A prevenção de injeção SQL é o conjunto de práticas de código e configuração que impede que uma entrada controlada por um atacante altere o significado de uma consulta ao banco de dados. A ideia central é a separação: o comando SQL é fixado pelo desenvolvedor, e qualquer valor fornecido pelo usuário viaja ao lado dele como dados puros que o motor nunca interpreta como instruções.

Essa separação é o jogo inteiro. Uma aplicação vulnerável trata a consulta como uma grande string que ela monta em tempo de execução, então um atacante que controla parte da string controla parte do comando. Uma aplicação defendida entrega ao banco de dados um modelo de consulta e um saco de valores, e o banco mantém os dois separados, não importa quais caracteres os valores contenham.

A injeção SQL corresponde à CWE-89, e suas correções são excepcionalmente bem estabelecidas. Não há nenhum framework engenhoso ou produto pago de que você precise. A defesa principal é um recurso de linguagem que existe em todo driver de banco de dados popular há vinte anos, e a maior parte da SQLi observada existe apenas porque alguém concatenou uma string em vez de usá-lo.

Por que a injeção SQL acontece: a causa raiz

Toda injeção SQL tem a mesma origem: a aplicação constrói uma consulta concatenando a entrada do usuário no texto SQL. No momento em que isso acontece, a fronteira entre código e dados desaparece, e o banco de dados interpreta o que o usuário enviou como parte do comando.

Aqui está o erro clássico, escrito da forma como ainda aparece em bases de código reais:

// Vulnerável: a entrada é concatenada direto na consulta
query = "SELECT * FROM users WHERE username = '" + name + "'";

Se name for alice, a consulta se comporta. Se name for ' OR '1'='1, a consulta vira SELECT * FROM users WHERE username = '' OR '1'='1', que é verdadeiro para toda linha. A entrada deixou de ser um nome de usuário e virou lógica. Nada na string diz ao banco de dados que a aspa do atacante deveria ser um dado.

É por isso que a lista de bloqueio de caracteres falha como estratégia. As equipes tentam remover aspas ou banir a palavra UNION, e os atacantes contornam com codificação, comentários, truques de maiúsculas/minúsculas ou sintaxe específica do banco. Você não consegue enumerar toda entrada perigosa, porque o problema não é a entrada, é a concatenação. Corrija o mecanismo e você para de se preocupar com os payloads.

💻
Pratique agora: lab Query Quake - explore uma consulta concatenada com injeção baseada em UNION para entender exatamente o que a parametrização tira de um atacante. No navegador, sem instalação.

Consultas parametrizadas: a defesa principal

Qual é a melhor forma de prevenir a injeção SQL? Use consultas parametrizadas, também chamadas de instruções preparadas. Você escreve o SQL com marcadores no lugar dos valores e passa os valores separadamente. O driver envia primeiro a estrutura da consulta ao banco de dados, então, quando os valores chegam, eles só podem ser dados. As aspas e palavras-chave de um atacante são armazenadas como uma string literal, nunca interpretadas como SQL.

O guia da OWASP sobre prevenção de injeção SQL lista isso como a primeira e mais forte defesa, e funciona da mesma forma em toda stack. A sintaxe muda, o princípio não.

PHP (PDO)

$stmt = $pdo->prepare('SELECT * FROM users WHERE username = ?');
$stmt->execute([$name]);
$user = $stmt->fetch();

Python (psycopg2 / sqlite3)

cur.execute("SELECT * FROM users WHERE username = %s", (name,))
user = cur.fetchone()

Repare na vírgula final: o segundo argumento é uma tupla de parâmetros, não formatação de string. Nunca use f"...{name}" nem % para montar o SQL, isso é concatenação disfarçada.

Java (PreparedStatement)

PreparedStatement ps = conn.prepareStatement(
    "SELECT * FROM users WHERE username = ?");
ps.setString(1, name);
ResultSet rs = ps.executeQuery();

Node.js (mysql2 / pg)

const [rows] = await conn.execute(
  'SELECT * FROM users WHERE username = ?', [name]);

Em todos os exemplos, o texto da consulta é uma constante que o desenvolvedor escreveu, e name chega por um argumento separado. Passe ' OR '1'='1 para qualquer um deles e o banco de dados procura um usuário literalmente chamado ' OR '1'='1, não encontra nenhum e não retorna nada. O ataque não tem onde aterrissar.

Mapeadores objeto-relacional como Hibernate, Entity Framework, SQLAlchemy e o ORM do Django parametrizam por padrão, o que explica em grande parte por que são mais seguros que o SQL escrito à mão. A armadilha é a saída de emergência: os métodos de consulta bruta e as cláusulas WHERE montadas por string dentro de um ORM são tão injetáveis quanto qualquer outra concatenação. Ao testar aplicações reais, são as bases de código muito orientadas a ORM onde olho com mais atenção para a única consulta bruta que alguém escreveu para um relatório que o ORM não conseguia expressar.

Validação de entrada e lista de permissão

A parametrização cobre valores. Ela não pode cobrir as partes de uma consulta que não são valores: nomes de tabelas, nomes de colunas e a direção de um ORDER BY. Você não pode vincular um nome de coluna como parâmetro, então, se um usuário puder escolher uma coluna de ordenação ou uma tabela, você precisa de outro controle.

Esse controle é a validação por lista de permissão. Compare a entrada com um conjunto fixo de opções conhecidas como boas e rejeite qualquer outra coisa, em vez de tentar higienizar o que chegou.

# Seguro: o usuário escolhe uma chave, o servidor é dono do identificador SQL
ALLOWED_SORT = {"name": "username", "date": "created_at"}
column = ALLOWED_SORT.get(user_choice, "username")
cur.execute(f"SELECT * FROM users ORDER BY {column}")

O usuário nunca fornece o nome da coluna. Ele fornece um rótulo, e o servidor o mapeia para um identificador fixo no código em que ele já confia. A f-string só é segura aqui porque column nunca pode ser nada além de um valor que o desenvolvedor escreveu.

A validação por lista de permissão é uma defesa de apoio, não um substituto das consultas parametrizadas. Use-a para identificadores e estrutura, use parâmetros para todo valor, e trate qualquer entrada que falhe na validação como um pedido a ser rejeitado, não limpo. Verificações de tipo também ajudam: se um ID deve ser um inteiro, converta-o e deixe um valor ruim falhar de forma barulhenta.

Menor privilégio e defesa em profundidade

As consultas parametrizadas param a injeção no código. Os controles abaixo limitam o dano caso alguma consulta escape, e são eles que transformam um provável vazamento em um incidente contido. Empilhe-os, porque nenhum controle isolado deveria ser a única coisa entre um atacante e seus dados.

  • Contas de banco de dados de menor privilégio. A conta com que sua aplicação web se conecta deve ter apenas os direitos de que precisa. Um endpoint de leitura não precisa de DELETE, DROP nem de acesso a outros schemas. Se uma injeção aterrissar mesmo assim, uma conta trancada limita o que ela pode alcançar.
  • Contas separadas por função. Não faça a aplicação inteira, as migrações e as ferramentas de administração passarem por um mesmo usuário todo-poderoso. Separe-os para que uma falha em um caminho não possa reescrever o banco de dados inteiro.
  • Tratamento seguro de erros. Nunca retorne os erros brutos do banco de dados ao cliente. Mensagens verbosas entregam ao atacante os nomes de tabelas, os tipos de colunas e a forma da consulta que transformam adivinhação às cegas em um ataque rápido e direcionado. Registre o detalhe no servidor e mostre uma mensagem genérica ao usuário.
  • Procedimentos armazenados, usados corretamente. Um procedimento armazenado só é seguro se usar parâmetros internamente. Um procedimento que concatena seus próprios argumentos em SQL dinâmico é exatamente tão vulnerável quanto código em linha, então a segurança vem da parametrização, não do procedimento em si.
  • Um WAF como rede de segurança, não como correção. Um firewall de aplicação web pode filtrar padrões óbvios de injeção e ganhar tempo contra a varredura automatizada, mas é um redutor de velocidade posto sobre as correções reais. Nunca deixe um WAF ser o motivo pelo qual uma consulta concatenada permanece na base de código.

Na prática, o menor privilégio é o controle que as equipes mais pulam porque a aplicação "simplesmente funciona" com uma conta de superusuário. Ela funciona até o momento em que uma injeção transforma esse superusuário na conta do atacante, e nesse instante a diferença entre acesso só de SELECT e controle total é a diferença entre uma anomalia registrada e uma manchete.

Como testar suas defesas contra injeção SQL

Como saber se sua prevenção realmente funciona? Ataque seu próprio código. Envie os payloads que um atacante enviaria, em um lab ou ambiente de teste autorizado, e confirme que a consulta os trata como dados inertes. Uma defesa que você nunca tentou quebrar é uma defesa cuja existência você apenas espera.

Uma rotina prática é assim:

  • Sonde manualmente primeiro. Solte uma única aspa, depois ' OR '1'='1, depois um UNION SELECT em cada entrada que chega a uma consulta. Em um endpoint parametrizado, você obtém um resultado vazio ou um "não encontrado" limpo, nunca um erro de banco de dados ou linhas extras.
  • Rode um scanner automatizado. Uma ferramenta como o sqlmap martelará os parâmetros com centenas de variações muito mais rápido do que você à mão. Aponte-a apenas para sistemas que você possui ou está autorizado a testar.
  • Revise o código, não só as respostas. Procure concatenação de strings perto das chamadas de consulta e as saídas de SQL bruto dentro do seu ORM. A maior parte da SQLi é óbvia no código-fonte quando você conhece o padrão a procurar.
  • Teste os identificadores, não apenas os valores. Parâmetros de ordenação, campos de filtro e tudo que aterrissa em uma posição de coluna ou tabela são os pontos que a parametrização não cobre. Confirme que esses passam por uma lista de permissão.

O objetivo de testar suas próprias defesas é que isso forja o instinto de detectar a falha em uma revisão de código, onde a prevenção é mais barata. Aprender a explorar SQLi e aprender a preveni-la são a mesma habilidade vista por dois lados, e fazer a metade ofensiva em um lab controlado é a forma mais rápida de internalizar a metade defensiva.

Lista de verificação para prevenção de injeção SQL

Uma lista curta e ordenada que você pode aplicar a qualquer base de código. Se o primeiro item estiver sólido em todos os lugares, o resto é reforço.

  1. Use consultas parametrizadas para todo valor. Nenhuma entrada de usuário é jamais concatenada no texto SQL. Esta é a correção que encerra a vulnerabilidade.
  2. Coloque em lista de permissão tudo que não puder parametrizar. Nomes de tabelas, nomes de colunas e direções de ordenação mapeiam a partir de um conjunto fixo de valores pertencentes ao servidor.
  3. Prefira um ORM, mas audite suas consultas brutas. Os métodos padrão do ORM parametrizam; a saída de SQL bruto não se policia sozinha.
  4. Rode a aplicação com uma conta de banco de dados de menor privilégio. Conceda apenas as operações de que o código realmente precisa.
  5. Esconda os erros de banco de dados dos usuários. Mensagem genérica para o cliente, detalhe completo nos logs do servidor.
  6. Teste, não presuma. Ataque suas próprias entradas em um lab e revise o código-fonte em busca de concatenação.

Considerações legais e éticas

Lembrete essencial: sempre obtenha autorização escrita explícita antes de rodar payloads de injeção SQL ou uma ferramenta como o sqlmap contra qualquer sistema. Testar uma aplicação que você não possui constitui acesso não autorizado sob o Computer Fraud and Abuse Act (EUA), o Computer Misuse Act (Reino Unido) e leis equivalentes no mundo todo, mesmo quando seu objetivo é apenas verificar se uma defesa resiste.

  • Teste apenas em sistemas que você possui, em alvos de treino propositalmente vulneráveis, ou dentro do escopo definido de um engajamento autorizado.
  • O trabalho de prevenção em código de produção é seguro e incentivado, mas confirmar um exploit contra dados reais não é. Reproduza a falha em uma cópia de homologação ou lab.
  • Se você encontrar uma injeção SQL na aplicação de outra pessoa, reporte-a pelo processo de divulgação dela. Não extraia dados para provar o impacto.
  • Os labs e cursos ligados aqui existem para que você possa praticar legalmente o ataque e a defesa, em alvos construídos exatamente para isso.

Perguntas frequentes

Qual é a forma mais eficaz de prevenir a injeção SQL?

As consultas parametrizadas, também chamadas de instruções preparadas, são a defesa mais eficaz. Você escreve o SQL com marcadores e passa a entrada do usuário como parâmetros separados, de modo que o banco de dados trata essa entrada como dados e nunca como parte do comando. Essa única prática, por si só, fecha a grande maioria das vulnerabilidades de injeção SQL.

As consultas parametrizadas param toda injeção SQL?

Elas param a injeção pelos valores da consulta, que é quase toda ela. Elas não cobrem partes de uma consulta que não podem ser parametrizadas, como nomes de tabelas, nomes de colunas e direção de ordenação. Para essas, use validação por lista de permissão que mapeia as escolhas do usuário para um conjunto fixo de identificadores controlados pelo servidor.

A validação de entrada é suficiente para prevenir a injeção SQL?

Não. A validação de entrada é uma camada de apoio útil, mas banir caracteres ou palavras-chave por lista de bloqueio pode ser contornado com codificação, comentários e sintaxe específica do banco. A correção confiável é a consulta parametrizada, que remove a concatenação que torna a injeção possível em primeiro lugar. Use validação junto dos parâmetros, não no lugar deles.

Os ORMs previnem a injeção SQL?

Na maior parte. ORMs como Hibernate, Entity Framework, SQLAlchemy e o ORM do Django parametrizam consultas por padrão, o que os torna seguros para uso padrão. O risco está em seus recursos de consulta bruta e em qualquer fragmento de consulta montado por string, que são tão injetáveis quanto SQL escrito à mão. Audite toda consulta bruta que seu código ORM contém.

Escapar a entrada do usuário previne a injeção SQL?

O escape é um último recurso, não uma defesa principal. Ele é propenso a erros, específico de cada banco de dados e fácil de errar de forma sutil, e a OWASP só o recomenda quando as consultas parametrizadas realmente não podem ser usadas. Se você se pega escapando a entrada para montar uma consulta, a melhor resposta quase sempre é parametrizá-la.

Seus próximos passos

A prevenção de injeção SQL é um dos raros problemas de segurança com uma resposta clara e estabelecida: pare de concatenar a entrada nas consultas e passe-a como parâmetro. Todo o resto deste guia, da lista de permissão de identificadores às contas de menor privilégio, é reforço em torno desse único hábito. O motivo de ainda falhar tantas vezes não é a correção ser difícil, é o padrão vulnerável ser mais rápido de digitar. A forma de tornar o padrão seguro automático é ver o ataque aterrissar uma vez. Comece com o plano gratuito da HackerDNA, sem cartão de crédito, e quebre você mesmo uma consulta concatenada no lab Query Quake ou no lab de injeção SQL dedicado. Quando quiser o quadro completo das vulnerabilidades web e suas defesas, o curso Web Attacks percorre a injeção SQL ao lado de XSS, SSRF e o resto do OWASP Top 10 em labs guiados no navegador. Aprenda a quebrar a consulta, depois escreva a versão que ninguém consegue quebrar.

HackerDNA Team

Equipe HackerDNA

Escrito pela equipe HackerDNA - profissionais de cibersegurança que criam labs práticos de hacking e conteúdo educativo para ajudar você a desenvolver habilidades reais em segurança.

Conhecer a Equipe

Pronto para colocar isso em prática?

Pare de ler, comece a hackear. Ganhe experiência prática com mais de 170 labs de cibersegurança reais.

Comece a Hackear Grátis
16.000+ Hackers 100+ Labs & Cursos Grátis
Comece Grátis