SQL injection: quando input vira comando de banco
A frase que uma pessoa digita num campo de busca não deveria ter poder nenhum sobre a QUERY em si — quando tem, é injeção de SQL.
SQL injection é um dos ataques mais antigos da web, e continua aparecendo até hoje pelo mesmo motivo: alguém monta uma query concatenando texto vindo do usuário direto na string SQL, achando que aquele texto é só dado. Pro banco de dados, texto dentro de uma string SQL não é "só dado" — é parte do comando que ele vai executar, palavra por palavra.
O clássico: ' OR '1'='1
Imagine um login construído concatenando email e senha direto numa query:
-- código do backend monta isto:
SELECT * FROM usuarios WHERE email = '${email}' AND senha = '${senha}'Num login normal, email e senha viram só os valores esperados. Mas se o campo email receber ' OR '1'='1, a query final fica assim:
SELECT * FROM usuarios WHERE email = '' OR '1'='1' AND senha = '''1'='1' é sempre verdadeiro — então a condição inteira do WHERE passa a selecionar todos os usuários da tabela, não só quem tem aquele email e senha específicos. O atacante não "adivinhou" a senha de ninguém; ele mudou o significado da query em si, transformando uma checagem de identidade numa condição sempre verdadeira.
A causa raiz: concatenar string onde deveria haver um parâmetro
O problema nunca é "o usuário digitou algo estranho" — é o código tratar o valor digitado como se fosse texto de comando confiável, em vez de um dado que só deveria ser comparado. Toda vez que uma query é montada por concatenação ou interpolação de string com valor externo, esse valor tem o poder de alterar a estrutura da query, não só o valor sendo buscado — não importa se é SELECT, UPDATE, ou DELETE.
Prepared statement / query parametrizada: por que já resolve por padrão
A defesa correta não é "filtrar aspas simples do input" (existem dezenas de variações de bypass pra esse tipo de filtro manual) — é nunca deixar o valor externo virar parte do texto da query. Uma prepared statement manda a estrutura da query e os valores separadamente pro banco: o banco compila o comando primeiro, sem nenhum valor ainda, e só depois substitui os placeholders — nesse ponto, não existe mais espaço pra um valor alterar a estrutura, porque a estrutura já foi fixada antes.
Usuario.where("email = '#{params[:email]}'")Usuario.where("email = ?", params[:email])Um Object-Relational Mapper (ORM) como o Active Record do Rails já usa placeholder por baixo dos panos toda vez que você chama métodos como where(coluna: valor) — na prática, um desenvolvedor precisa escrever SQL cru de propósito (via interpolação de string) pra reabrir essa vulnerabilidade. É um dos raros casos em que a ferramenta já protege por padrão, e o erro exige um passo extra e deliberado pra acontecer.
Exercício
Um endpoint de busca de produtos recebe params[:categoria] e monta a query como Produto.where("categoria = '#{params[:categoria]}'"). Reescreva usando parâmetro, e explique o que um atacante conseguiria fazer com categoria = "x' OR '1'='1" na versão vulnerável.