# Evitar SQL Injection em PHP

> Evitar SQL Injection em PHP: Prepared Statements com PDO e mysqli, listas de permissão para colunas, permissões mínimas na base de dados. Com exemplos de código.

Fonte: https://phpsos.de/pt/ratgeber/sql-injection-php/  
Atualizado em: 25/10/2026  
Prestador: PHP SOS, Assistência de emergência PHP e segurança de servidores. Ajuda imediata por WhatsApp: https://wa.me/491736494914?text=Ol%C3%A1%2C%20PHP%20SOS%2C%20precisamos%20de%20ajuda%20com%20o%20nosso%20servidor%20PHP.%20Empresa%3A%20%E2%80%A6%20Site%3A%20%E2%80%A6

**Em resumo:** evita a SQL Injection em PHP de forma fiável nunca inserindo valores em textos SQL, mas passando-os sempre através de Prepared Statements com marcadores de posição. Para nomes de colunas e de tabelas, que não podem ser passados como marcadores, aplica-se uma lista de permissão fixa. Adicionalmente, um utilizador de base de dados com permissões mínimas limita o dano, caso ainda reste uma vulnerabilidade.

## Porque é que a SQL Injection não pode sair do radar após o PHP2Shell

Em outubro de 2026, muita coisa gira à volta do PHP2Shell, duas vulnerabilidades críticas no interpretador PHP, corrigidas desde 21/10/2026 no PHP 8.5.12, 8.4.27, 8.3.36 e 8.2.35. Muitas equipas atualizaram servidores e reiniciaram processos nesses dias. Isso está certo e é urgente.

Mas um estado atual do PHP nada diz sobre o código próprio. A SQL Injection é há anos uma das fragilidades mais frequentes em aplicações web e consta do OWASP Top 10 na categoria Injection. Quem de qualquer modo planeia verificações de segurança deve olhar logo também para os acessos à base de dados da própria aplicação.

A boa notícia: a SQL Injection é uma das poucas fragilidades que se podem excluir quase por completo com uma regra simples, aplicada de forma consequente.

## Como surge a SQL Injection em PHP?

A SQL Injection surge quando uma aplicação trata as entradas do utilizador como parte de um comando SQL, em vez de dados puros. Típico é compor uma consulta a partir de blocos de texto e variáveis. Se um visitante puder determinar o conteúdo de uma dessas variáveis, pode, em certas circunstâncias, alterar o significado do comando.

As consequências vão da leitura de dados de clientes alheios à alteração de preços ou permissões e à eliminação de tabelas inteiras. Consoante a base de dados e as permissões, uma vulnerabilidade pode também permitir acessos para além da base de dados. Se estiverem afetados dados pessoais, pode surgir uma obrigação de notificação nos termos do RGPD.

As entradas não são apenas campos de formulário. Também parâmetros de URL, cookies, cabeçalhos HTTP, ficheiros carregados, respostas de interfaces alheias e dados já guardados na própria base de dados são considerados não fiáveis.

## Prepared Statements com PDO e mysqli

Os Prepared Statements enviam o comando SQL e os valores em separado para a base de dados. Os valores nunca são interpretados como parte do comando. Com PDO, uma consulta segura é assim:

```php
<?php
$pdo = new PDO($dsn, $benutzer, $passwort, [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_EMULATE_PREPARES   => false,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);

$stmt = $pdo->prepare('SELECT id, name, email FROM kunden WHERE id = :id AND status = :status');
$stmt->execute([
    'id'     => (int) $_GET['id'],
    'status' => 'aktiv',
]);
$kunde = $stmt->fetch();
```

A opção `PDO::ATTR_EMULATE_PREPARES => false` faz com que a base de dados use verdadeiros Prepared Statements, desde que o controlador o suporte. Com mysqli aplica-se o mesmo princípio:

```php
<?php
$stmt = $mysqli->prepare('SELECT id, name FROM produkte WHERE kategorie = ? AND preis <= ?');
$stmt->bind_param('sd', $kategorie, $maxPreis);
$stmt->execute();
$ergebnis = $stmt->get_result();
```

Também as listas para `IN (...)` podem ser representadas com segurança, criando um marcador próprio para cada valor e passando os valores em separado.

```php
<?php
$ids = array_map('intval', $ausgewaehlteIds);
$platzhalter = implode(',', array_fill(0, count($ids), '?'));
$stmt = $pdo->prepare("SELECT id, name FROM produkte WHERE id IN ($platzhalter)");
$stmt->execute($ids);
```

## Casos especiais: nomes de colunas, ordenação e SQL em bruto

Os marcadores funcionam apenas para valores, não para nomes de tabelas, nomes de colunas ou sentidos de ordenação. É precisamente aqui que, na prática, surgem muitas vulnerabilidades, por exemplo em tabelas ordenáveis ou filtros. A solução é uma lista fixa de valores permitidos:

```php
<?php
$erlaubteSpalten = ['name' => 'name', 'preis' => 'preis', 'datum' => 'erstellt_am'];
$spalte   = $erlaubteSpalten[$_GET['sort'] ?? ''] ?? 'name';
$richtung = (($_GET['dir'] ?? '') === 'desc') ? 'DESC' : 'ASC';

$stmt = $pdo->query("SELECT id, name, preis FROM produkte ORDER BY $spalte $richtung");
```

As frameworks e os CMS trazem ferramentas próprias. No WordPress, o `$wpdb->prepare()` assume esta tarefa, no Laravel e no Symfony os query builders e ORMs. Assim que usar aí SQL em bruto, por exemplo através de funções com “raw” no nome, voltam a aplicar-se as regras desta secção. Indicações sobre o Laravel encontra-as em [Proteger aplicações Laravel](https://phpsos.de/pt/plattformen/laravel/).

> **Dica:**
> Procure no código, de forma orientada, textos SQL em que se inserem variáveis. Um começo simples é uma pesquisa por `query(` e `->raw` em combinação com `$_GET`, `$_POST` ou `$_REQUEST`.

## Detetar SQL Injection no código existente

Em software novo, a regra pode ser cumprida desde o início. É mais difícil em aplicações que cresceram ao longo de anos. Aí vale a pena uma pesquisa sistemática dos pontos em que textos SQL são compostos com variáveis:

```bash
# Comandos SQL em que se inserem variáveis
grep -rnE "(SELECT|INSERT|UPDATE|DELETE)[^;]*\\\$[a-zA-Z_]" src/ --include="*.php" | head -50
# Utilização direta de dados do pedido em funções de consulta
grep -rnE "(query|exec|raw)\s*\(.*\\\$_(GET|POST|REQUEST|COOKIE)" src/ --include="*.php"
```

Nem todo o resultado é uma vulnerabilidade, por exemplo quando a variável contém apenas um nome de tabela fixo. Cada ocorrência deve, porém, ser verificada conscientemente uma vez e, se necessário, convertida para Prepared Statements. As ferramentas de análise estática de código ajudam a detetar estes padrões de forma permanente no pipeline de build, de modo que novos erros nem cheguem à produção.

## Limitar o dano: permissões, mensagens de erro, verificação

Código seguro é a primeira linha de defesa. Outros níveis limitam o dano, caso uma vulnerabilidade passe despercebida:

- **Permissões mínimas:** o utilizador de base de dados do site precisa geralmente apenas de `SELECT`, `INSERT`, `UPDATE` e `DELETE` na base de dados própria, sem permissões para criar utilizadores ou aceder a outras bases de dados.
- **Sem mensagens de erro para o exterior:** `display_errors = Off` no php.ini. Os erros da base de dados pertencem ao registo, não ao navegador.
- **Separação por aplicação:** cada site recebe um utilizador de base de dados próprio.
- **Manter as dependências atualizadas:** há vulnerabilidades também em bibliotecas, como mostra o guia [composer audit](https://phpsos.de/pt/ratgeber/composer-abhaengigkeiten-pruefen/).
- **Mandar verificar:** a revisão de código e a análise automatizada encontram muitos pontos; um teste de intrusão mostra o que é realmente acessível a partir do exterior.

```sql
-- Exemplo: utilizador de base de dados apenas com as permissões necessárias
CREATE USER 'shop_app'@'localhost' IDENTIFIED BY '<starkes Passwort>';
GRANT SELECT, INSERT, UPDATE, DELETE ON shop.* TO 'shop_app'@'localhost';
```

Uma fragilidade aparentada diz respeito à saída de dados no navegador. Como a proteger, explica-o o guia [Evitar Cross-Site Scripting em PHP](https://phpsos.de/pt/ratgeber/xss-php/).

## Como ajuda concretamente a PHP SOS?

- Verificamos de forma sistemática os acessos à base de dados da sua aplicação quanto a consultas inseguras.
- Priorizamos os achados segundo a acessibilidade e os dados afetados e mostramos correções concretas.
- Levamos as permissões da base de dados e o tratamento de erros a um nível seguro.
- Voltamos a testar após a correção e documentamos o resultado.

Uma verificação independente é descrita no serviço [Auditoria de segurança e teste de intrusão](https://phpsos.de/pt/leistungen/sicherheitsaudit-penetrationstest/).


## Perguntas frequentes

**Basta tratar as entradas com addslashes ou real_escape_string?**  
Não, é propenso a erros. O escaping depende do conjunto de caracteres e do contexto e é facilmente esquecido num ponto. Os Prepared Statements separam, por princípio, o comando e os dados e são, por isso, o caminho recomendado.

**Um ORM protege automaticamente contra SQL Injection?**  
Em grande medida, desde que utilize as funções de consulta do ORM. Assim que escreve SQL em bruto ou insere valores em ordenações e nomes de colunas, aplicam-se as mesmas regras que sem ORM.

**Uma Web Application Firewall ajuda contra a SQL Injection?**  
Uma WAF pode intercetar padrões conhecidos e ganhar tempo. Mas não substitui código seguro, porque as regras baseadas em dados do pedido podem ser contornadas e por vezes atingem entradas legítimas.

**A atualização do PHP contra o PHP2Shell também fecha vulnerabilidades de SQL Injection?**  
Não. O PHP2Shell afeta o interpretador PHP. A SQL Injection é um erro no código da aplicação e mantém-se, seja qual for a versão do PHP em execução.

## Fontes

- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
- [OWASP Cheat Sheet Series](https://cheatsheetseries.owasp.org/)
- [php.net: avisos de segurança](https://www.php.net/)
