<h2>O Problema: SQL Injection</h2>
<p>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.</p>
<p>Considere este exemplo vulnerável em PHP com MySQLi procedural:</p>
<pre><code class="language-php"><?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);
?></code></pre>
<p>Se o atacante inserir <code>' OR '1'='1</code> no campo usuário, a query fica: <code>SELECT * FROM usuarios WHERE usuario = '' OR '1'='1' AND senha = '...'</code>. 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.</p>
<h2>Prepared Statements: A Solução Padrão</h2>
<p>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.</p>
<h3>Como Funcionam</h3>
<p>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.</p>
<h3>Implementação em PHP com MySQLi</h3>
<pre><code class="language-php"><?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);
?></code></pre>
<p>O <code>"ss"</code> 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 <code>' OR '1'='1</code>, será tratado como string literal.</p>
<h3>Implementação em Python com SQLite</h3>
<pre><code class="language-python">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()</code></pre>
<p>Python com o módulo <code>sqlite3</code> também usa prepared statements nativamente. Os dados são passados como tupla separada da query. O banco de dados nunca interpreta <code>(usuario, senha)</code> como código.</p>
<h3>Implementação com ORM (Django/SQLAlchemy)</h3>
<pre><code class="language-python">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])</code></pre>
<p>ORMs (Object-Relational Mapping) abstraem prepared statements e são mais seguras por padrão. Evite usar <code>.raw()</code> com concatenação de strings.</p>
<h2>Boas Práticas e Validação Complementar</h2>
<p>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.</p>
<h3>Validação de Entrada</h3>
<p>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.</p>
<pre><code class="language-python">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)))</code></pre>
<h3>Sanitização de Saída</h3>
<p>Dados exibidos ao usuário podem conter HTML/JavaScript malicioso (XSS). Use funções de escape:</p>
<pre><code class="language-php"><?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;
?></code></pre>
<h3>Princípio do Menor Privilégio</h3>
<p>Use contas de banco de dados com permissões mínimas. O usuário da aplicação não precisa de acesso <code>DROP TABLE</code>.</p>
<pre><code class="language-sql">-- 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</code></pre>
<h2>Conclusão</h2>
<p><strong>Prepared Statements são obrigatórios</strong> 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.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://owasp.org/Top10/A03_2021-Injection/" target="_blank" rel="noopener noreferrer">OWASP Top 10 - A03:2021 Injection</a></li>
<li><a href="https://www.php.net/manual/en/mysqli.quickstart.prepared-statements.php" target="_blank" rel="noopener noreferrer">PHP: Prepared Statements (MySQLi)</a></li>
<li><a href="https://docs.python.org/3/library/sqlite3.html" target="_blank" rel="noopener noreferrer">Python sqlite3 Documentation</a></li>
<li><a href="https://www.postgresql.org/docs/current/sql-prepare.html" target="_blank" rel="noopener noreferrer">PostgreSQL: Prepared Statements</a></li>
<li><a href="https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html" target="_blank" rel="noopener noreferrer">OWASP: SQL Injection Prevention Cheat Sheet</a></li>
</ul>