Como Usar Prepared Statements e Prevenção de SQL Injection em Produção Já leu

O Problema: SQL Injection SQL Injection é uma das vulnerabilidades mais críticas em aplicações web. Ocorre quando um atacante consegue inserir comandos SQL maliciosos através de inputs que não são validados adequadamente. Imagine um formulário de login simples: se o desenvolvedor concatenar strings diretamente na query, um atacante pode alterar completamente a lógica SQL. Considere este exemplo vulnerável em PHP com MySQLi procedural: Se o atacante inserir no campo usuário, a query fica: . Isso retorna todos os usuários, pulando a autenticação completamente. A consecução é devastadora: roubo de dados, deleção de registros, escalação de privilégios. Prepared Statements: A Solução Padrão Prepared Statements (ou consultas preparadas) separaram a estrutura SQL dos dados. O servidor de banco de dados primeiro recebe a estrutura da query com placeholders, depois os dados são enviados separadamente. Isso garante que dados nunca sejam interpretados como código SQL. Como Funcionam A query é enviada em duas etapas. Primeiro, o template é compilado no servidor. Segundo, os

O Problema: SQL Injection

SQL Injection é uma das vulnerabilidades mais críticas em aplicações web. Ocorre quando um atacante consegue inserir comandos SQL maliciosos através de inputs que não são validados adequadamente. Imagine um formulário de login simples: se o desenvolvedor concatenar strings diretamente na query, um atacante pode alterar completamente a lógica SQL.

Considere este exemplo vulnerável em PHP com MySQLi procedural:

<?php
$usuario = $_POST['usuario'];
$senha = $_POST['senha'];

// NUNCA FAÇA ASSIM!
$query = "SELECT * FROM usuarios WHERE usuario = '" . $usuario . "' AND senha = '" . $senha . "'";
$resultado = mysqli_query($conexao, $query);
?>

Se o atacante inserir ' OR '1'='1 no campo usuário, a query fica: SELECT * FROM usuarios WHERE usuario = '' OR '1'='1' AND senha = '...'. Isso retorna todos os usuários, pulando a autenticação completamente. A consecução é devastadora: roubo de dados, deleção de registros, escalação de privilégios.

Prepared Statements: A Solução Padrão

Prepared Statements (ou consultas preparadas) separaram a estrutura SQL dos dados. O servidor de banco de dados primeiro recebe a estrutura da query com placeholders, depois os dados são enviados separadamente. Isso garante que dados nunca sejam interpretados como código SQL.

Como Funcionam

A query é enviada em duas etapas. Primeiro, o template é compilado no servidor. Segundo, os parâmetros são passados de forma segura. O banco de dados sabe exatamente qual é a estrutura e qual é o dado, impossibilitando injeção.

Implementação em PHP com MySQLi

<?php
$conexao = mysqli_connect("localhost", "user", "password", "database");

$usuario = $_POST['usuario'];
$senha = $_POST['senha'];

// Prepared Statement com placeholders (?)
$stmt = mysqli_prepare($conexao, "SELECT * FROM usuarios WHERE usuario = ? AND senha = ?");
mysqli_stmt_bind_param($stmt, "ss", $usuario, $senha);
mysqli_stmt_execute($stmt);
$resultado = mysqli_stmt_get_result($stmt);

while($row = mysqli_fetch_assoc($resultado)) {
    echo "Bem-vindo: " . htmlspecialchars($row['usuario']);
}

mysqli_stmt_close($stmt);
mysqli_close($conexao);
?>

O "ss" indica que esperamos duas strings. As variáveis são passadas por referência e vinculadas aos placeholders antes da execução. Mesmo que o usuário digite ' OR '1'='1, será tratado como string literal.

Implementação em Python com SQLite

import sqlite3

conexao = sqlite3.connect('banco.db')
cursor = conexao.cursor()

usuario = input("Usuário: ")
senha = input("Senha: ")

# Prepared Statement com ? placeholders
query = "SELECT * FROM usuarios WHERE usuario = ? AND senha = ?"
cursor.execute(query, (usuario, senha))

for linha in cursor.fetchall():
    print(f"Bem-vindo: {linha[0]}")

conexao.close()

Python com o módulo sqlite3 também usa prepared statements nativamente. Os dados são passados como tupla separada da query. O banco de dados nunca interpreta (usuario, senha) como código.

Implementação com ORM (Django/SQLAlchemy)

from django.db import models
from django.contrib.auth.models import User

# Em Django, queries via ORM usam prepared statements automaticamente
usuario = User.objects.filter(username=usuario_input, password=senha_input).first()

# Equivalente manual seria:
# User.objects.raw("SELECT * FROM auth_user WHERE username = %s AND password = %s", [usuario_input, senha_input])

ORMs (Object-Relational Mapping) abstraem prepared statements e são mais seguras por padrão. Evite usar .raw() com concatenação de strings.

Boas Práticas e Validação Complementar

Prepared Statements resolvem SQL Injection, mas não são a única camada de defesa. Validação de entrada e sanitização de saída complementam a segurança.

Validação de Entrada

Sempre valide o tipo, tamanho e formato esperado. Se espera um email, rejeite valores que não sejam emails. Se espera um inteiro, converta e valide antes de usar.

import re
from datetime import datetime

def validar_email(email):
    padrao = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
    return re.match(padrao, email) is not None

def validar_idade(idade_str):
    try:
        idade = int(idade_str)
        return 0 <= idade <= 150
    except ValueError:
        return False

email = input("Email: ")
if not validar_email(email):
    print("Email inválido!")
    exit()

idade = input("Idade: ")
if not validar_idade(idade):
    print("Idade inválida!")
    exit()

# Agora é seguro usar em prepared statement
cursor.execute("INSERT INTO clientes (email, idade) VALUES (?, ?)", (email, int(idade)))

Sanitização de Saída

Dados exibidos ao usuário podem conter HTML/JavaScript malicioso (XSS). Use funções de escape:

<?php
$usuario = "João <script>alert('xss')</script>";

// XSS Prevention
echo htmlspecialchars($usuario, ENT_QUOTES, 'UTF-8');
// Output: João &lt;script&gt;alert('xss')&lt;/script&gt;
?>

Princípio do Menor Privilégio

Use contas de banco de dados com permissões mínimas. O usuário da aplicação não precisa de acesso DROP TABLE.

-- Cria usuário apenas para SELECT, INSERT, UPDATE em tabelas específicas
CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'senha_forte';
GRANT SELECT, INSERT, UPDATE ON database.usuarios TO 'app_user'@'localhost';
-- Nunca concede DELETE ou DROP

Conclusão

Prepared Statements são obrigatórios para qualquer aplicação que acesse banco de dados. Eles eliminam a raiz do SQL Injection ao separar lógica de dados. Nenhuma quantidade de validação manual substitui prepared statements — use-os em 100% das queries que envolvem entrada de usuário. Combine com validação de entrada, sanitização de saída e princípio do menor privilégio para uma defesa em profundidade. Negligenciar isso é negligenciar a segurança dos seus usuários.

Referências


Artigos relacionados