flawopen.com/Injeção de SQL/Python
Imagine um formulário que só espera um número de bilhete, como 482. A injeção de SQL acontece quando alguém digita algo malicioso nessa caixa em vez de um número — uma frase armadilhada que faz o sistema responder "mostre todos os bilhetes" em vez de apenas o bilhete 482, porque o sistema nunca verificou se o que recebeu era realmente apenas um número.
A injeção de SQL acontece quando entrada controlada pelo usuário é inserida diretamente no texto de uma consulta ao banco de dados, em vez de ser passada como um valor separado. Se o código monta a consulta colando strings, um atacante pode fornecer uma entrada que altera a estrutura real da consulta — transformando uma consulta de "buscar uma linha" em uma que retorna todas as linhas, ou apaga uma tabela.
Em Python, isso quase sempre aparece da mesma forma: uma chamada ao banco de dados construída com f-string, formatação com %, ou concatenação com +, em vez dos placeholders de parâmetros embutidos no driver do banco.
Em 2015, a operadora britânica TalkTalk sofreu um vazamento que afetou mais de 150 mil clientes depois que atacantes exploraram uma falha de injeção de SQL em uma página web legada herdada de uma aquisição. O órgão regulador de proteção de dados do Reino Unido multou a TalkTalk em £400.000, descrevendo a falha como evitável e básica.
Fonte: notificação de aplicação do Information Commissioner's Office do Reino Unido, 2016 — ver Referências abaixo.# user_id vem diretamente da requisição
def get_user(cursor, user_id):
query = f"SELECT * FROM users WHERE id = {user_id}"
cursor.execute(query)
return cursor.fetchone()
# o valor é passado separadamente, nunca embutido
def get_user(cursor, user_id):
query = "SELECT * FROM users WHERE id = %s"
cursor.execute(query, (user_id,))
return cursor.fetchone()
A versão corrigida passa o texto da consulta e o valor para execute() como dois argumentos separados. O driver do banco de dados também os envia separadamente ao banco — a estrutura da consulta é fixada antes que o valor seja anexado a ela, então o valor nunca pode ser interpretado como parte da sintaxe SQL, não importa quais caracteres contenha. Uma f-string não consegue fazer isso, porque, no momento em que execute() recebe a consulta, o valor já foi incorporado ao texto como se sempre tivesse feito parte do comando.
cursor.execute("... WHERE id = %s" % user_id) é tão vulnerável quanto uma f-string. O placeholder só se torna seguro quando o valor é passado como o próprio segundo argumento de execute() — cursor.execute("...WHERE id = %s", (user_id,)) — para que seja o driver, e não a formatação de strings do Python, a fazer a substituição.
O ORM do Django e o query builder do SQLAlchemy parametrizam automaticamente em consultas normais. O risco volta no momento em que você usa Model.objects.raw() ou text() do SQLAlchemy e monta esse SQL bruto com uma f-string.
psycopg2 (PostgreSQL) usa %s independentemente do tipo da coluna; sqlite3 usa ?. Copiar o estilo de placeholder da documentação de um driver para outro quebra silenciosamente — verifique o estilo de parâmetro do seu driver específico em vez de presumir.
Verdadeiro para a API de consulta normal do ORM — falso no momento em que você usa raw() ou text() e monta essa string você mesmo.
O risco não é o tipo do valor em tempo de execução — é o fato de a consulta ser construída por interpolação de string. No momento em que essa suposição falhar em qualquer ponto do ciclo de vida do código, a vulnerabilidade já estará lá esperando.
O escape manual é específico do driver e fácil de errar de forma sutil. Consultas parametrizadas não são uma versão mais rigorosa do escape — elas evitam o problema por completo, porque o valor nunca faz parte do texto da consulta.
grep -rn "execute(f\"" --include="*.py" .
grep -rn "execute(.*%\s*(" --include="*.py" .
grep -rn "\.raw(\|text(" --include="*.py" .
Para seus métodos de consulta normais, sim. As válvulas de escape para consultas brutas não protegem — elas são exatamente tão seguras quanto SQL escrito à mão, nem mais.
Não. Qualquer coisa efetivamente controlada por terceiros conta — cabeçalhos HTTP, nomes de arquivos enviados, até um valor vindo de uma API de terceiros em que sua aplicação confia.
Você pode, mas é frágil e específico do driver. Consultas parametrizadas são a correção de fato, não uma versão mais rigorosa do escape.