Injeção SQL
As injeções SQL representam uma grande ameaça para os modelos de bases de dados relacionais e para a informação que estas armazenam. Por isso, é fundamental dispor de uma proteção completa contra estes acessos externos não autorizados que se aproveitam de vulnerabilidades de segurança.
O que é uma injeção SQL?
Uma injeção SQL (do inglês SQL injection) define-se como a exploração de uma vulnerabilidade em bases de dados relacionais que utilizam a linguagem de consulta SQL. O atacante aproveita-se das entradas do utilizador que não são devidamente filtradas e que contêm metacaracteres, como o traço duplo, as aspas ou o ponto e vírgula. Estes caracteres representam funções especiais para o interpretador de SQL e permitem a influência externa sobre as instruções executadas. É comum que as injeções SQL sejam realizadas em conjunto com programas PGP e ASP, que dependem de interfaces mais antigas. Nestas, algumas das entradas não possuem o mascaramento correto, tornando-se assim o alvo perfeito para estes ataques.
Através do uso intencional de caracteres especiais, um utilizador não autorizado pode injetar comandos SQL adicionais e manipular as entradas de forma a modificar, eliminar ou ler dados. Nos casos mais graves, um atacante pode até obter acesso à linha de comandos do sistema que executa os comandos e, consequentemente, ao servidor de bases de dados na sua totalidade.
Exemplos de injeções SQL: é assim que funcionam os ataques a bases de dados
Uma vez que os servidores de bases de dados podem ser facilmente localizados, da mesma forma que é fácil executar ataques de injeção SQL, este método tornou-se rapidamente um dos preferidos dos cibercriminosos em todo o mundo. Estes atuam com diferentes padrões de ataque e aproveitam-se, sobretudo, das vulnerabilidades tradicionais e atuais das aplicações envolvidas no processo de gestão de dados. A seguir, e para explicar como funcionam estes ataques, apresentam-se, a título de exemplo, os dois métodos mais comuns.
Exemplo 1: acesso através de uma entrada do utilizador mal enmascarada
Normalmente, antes de um utilizador poder aceder a uma base de dados, é necessário que se autentique. Para esse efeito, existem scripts que, por exemplo, criam um formulário de início de sessão composto por um nome de utilizador e uma palavra-passe. Os utilizadores preenchem o formulário e, em seguida, o script verifica se existem entradas correspondentes na base de dados. Por predefinição, a base de dados contém uma tabela chamada users com as colunas username e password. Para qualquer aplicação web, as linhas de comando (pseudocódigo) que permitem o acesso ao servidor web seriam:
uname = request.POST['username']
passwd = request.POST['password']
sql = "SELECT id FROM users WHERE username='" + uname + "' AND password='" + passwd + "'"
database.execute(sql)pythonUm atacante tem agora a possibilidade de manipular intencionalmente o campo da palavra-passe através de uma injeção SQL, introduzindo, por exemplo, password' OR 1='1, o que resultaria na seguinte consulta SQL:
sql = "SELECT id FROM users WHERE username='' AND password='password' OR 1='1'"pythonAssim, o atacante obtém acesso ao registo completo de utilizadores na base de dados, uma vez que a palavra-passe é sempre válida (1=‘1’). Agora, pode registar-se como administrador e efetuar alterações nas entradas. O campo do nome de utilizador também pode ser manipulado da mesma forma.
Exemplo 2: espionagem de dados através da manipulação de identificadores
Solicitar informações de uma base de dados através de um identificador é um método prático e comum, bem como uma potencial porta de entrada para um ataque de injeção SQL. Por exemplo, o identificador transmitido numa URL permite ao servidor web identificar quais as informações que deve recuperar da base de dados. O script PHP para tal terá o seguinte aspeto:
<?php
$mysqli = new mysqli("localhost", "nombre_usuario", "contraseña", "base_de_datos");
$id = intval($_GET['id']);
$result = $mysqli->query("SELECT * FROM tabla WHERE id=$id");
while ($row = $result->fetch_assoc()) {
echo print_r($row, true);
}
?>phpA URL esperada tem o formato .../script.php?id=22. Nesse caso, seria consultado o registo da tabela com o ID «22». No entanto, se uma pessoa externa tiver a possibilidade de manipular essa URL e enviar ao servidor web a solicitação .../script.php?id=22+OR+1=1, a consulta resultante fará com que todos os dados da tabela sejam lidos:
SELECT * FROM tabla WHERE id=22 OR 1=1;sqlComo é que os cibercriminosos identificam as bases de dados vulneráveis?
Em princípio, qualquer página web ou aplicação que utilize bases de dados SQL sem instruções preparadas (prepared statements) ou outros mecanismos de proteção pode estar vulnerável a injeções SQL. As vulnerabilidades descobertas não permanecem em segredo por muito tempo no vasto mundo da Internet; na verdade, existem páginas que compilam falhas de segurança atuais e mostram até mesmo como encontrar projetos web vulneráveis através de pesquisas no Google. Se uma página devolver mensagens de erro SQL detalhadas, os cibercriminosos podem aproveitar essa informação para detetar possíveis pontos fracos. Por exemplo, basta adicionar uma aspa simples no final de um URL com um parâmetro de ID (como no exemplo seguinte):
[Domainname].com/news.php?id=5‘Uma simples apóstrofe no identificador da URL faz com que uma página web vulnerável exiba uma mensagem de erro como esta:
Query failed: You have an error in your SQL Syntax …
Em português: «Erro na solicitação: a sintaxe do seu SQL está incorreta…». Com métodos semelhantes, também é possível revelar o número de colunas, os nomes das tabelas e das colunas, a versão do SQL e até mesmo nomes de utilizador e palavras-passe. Além disso, existem ferramentas que permitem automatizar tanto a pesquisa como as injeções SQL subsequentes.
Como proteger a sua base de dados contra uma injeção SQL
Existem várias medidas para prevenir ataques de injeção SQL e proteger a base de dados. Para tal, é necessário familiarizar-se com todos os componentes envolvidos: o servidor, as aplicações individuais e o sistema de gestão de bases de dados.
Passo 1: monitorizar as alterações automáticas nas aplicações
Ao processar entradas provenientes de aplicações externas ou integradas, é fundamental validar e filtrar os valores enviados para evitar ataques de injeção SQL.
1. Verificar os tipos de dados
Cada dado introduzido deve corresponder ao tipo esperado. Por exemplo, se for esperado um número inteiro, uma validação simples em PHP poderia ser assim:
if (filter_var($input, FILTER_VALIDATE_INT) === false) {
throw new InvalidArgumentException("Entrada no válida");
}phpDevem ser aplicadas verificações semelhantes a cadeias de texto, datas ou outros formatos específicos.
2. Filtrar caracteres especiais
Os caracteres especiais podem causar vulnerabilidades, especialmente em contextos SQL ou HTML. Uma forma segura de lidar com eles é utilizar htmlspecialchars() para entradas HTML e PDO::quote() para consultas SQL.
3. Evitar mensagens de erro detalhadas
Não devem ser apresentadas mensagens de erro que revelem informações técnicas sobre a base de dados ou o sistema. Em vez disso, é preferível apresentar uma mensagem genérica como esta:
echo "Se ha producido un error. Por favor, inténtalo de nuevo más tarde.";
error_log("Se ha producido un error inesperado. Consulta el registro del sistema para más detalles.");php4. Utilizar instruções preparadas (prepared statements)
Uma forma segura de evitar injeções SQL é através da utilização de consultas preparadas (ou prepared statements), como já foi referido. Este método separa as instruções SQL dos parâmetros, impedindo assim a execução de código malicioso. Um exemplo em PHP com PDO (PHP Data Objects) seria:
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id");
$stmt->bindParam(':id', $user_id, PDO::PARAM_INT);
$stmt->execute();phpO sistema de gestão de bases de dados encarrega-se automaticamente de processar os dados de forma segura.
Passo 2: garantir uma proteção completa ao servidor
Logicamente, a segurança do servidor no qual o sistema de gestão de bases de dados é executado desempenha um papel fundamental na prevenção de ataques de injeção SQL. A primeira medida de segurança consiste no reforço do sistema operativo, de acordo com o seguinte padrão:
- Instale e ative apenas as aplicações e os serviços que sejam realmente relevantes para a base de dados.
- Elimine todas as contas de utilizador que não sejam necessárias.
- Certifique-se de instalar todas as atualizações relevantes para o sistema e o programa.
- Aplique o princípio do privilégio mínimo para garantir que os utilizadores e serviços tenham apenas as permissões estritamente necessárias.
De acordo com os requisitos de segurança do seu projeto web, convém considerar também medidas de proteção adicionais:
- Sistemas de deteção de intrusões (IDS) e sistemas de prevenção de intrusões (IPS): estes sistemas utilizam diferentes métodos de deteção para identificar ataques ao servidor numa fase inicial, emitir alertas e, no caso dos IPS, aplicar automaticamente medidas de resposta.
- Application Layer Gateway (ALG): um gateway de camada de aplicação, ou ALG, monitoriza e filtra o tráfego de dados entre as aplicações e os navegadores web diretamente na camada de aplicação.
- Firewall de aplicações web (WAF): um WAF protege especificamente as aplicações web contra injeções SQL e Cross Site Scripting (XSS), bloqueando ou neutralizando pedidos suspeitos.
- Abordagem Zero Trust: esta abordagem de segurança moderna garante que todo o acesso seja verificado e validado, independentemente da sua origem, antes de ser autorizado.
- Regras de firewall e segmentação de rede: estas medidas são fundamentais para reduzir a superfície de ataque a longo prazo.
- Auditorias de segurança de TI periódicas e testes de penetração: estas ações ajudam a identificar e corrigir vulnerabilidades antes que possam ser exploradas.
Passo 3: proteger a base de dados e utilizar códigos mais seguros
Tal como um sistema operativo, uma base de dados deve estar livre de fatores externos irrelevantes e ser atualizada regularmente. Para tal, é recomendável desativar todos os serviços e contas de utilizador inativos, bem como eliminar todas as rotinas armazenadas que não sejam necessárias. Configure uma conta de base de dados destinada exclusivamente ao acesso através da Web e com direitos de acesso mínimos.
Seguindo as boas práticas para consultas, recomenda-se vivamente que não utilize o módulo mysql do PHP (eliminado a partir do PHP 7) e opte, em vez disso, pelo mysqli ou pelo PDO. Desta forma, também poderá proteger a sua aplicação com código mais seguro. Uma consulta segura com mysqli ficaria, por exemplo, assim:**
$mysqli = new mysqli("localhost", "usuario", "contraseña", "base_de_datos");
if ($mysqli->connect_error) die("Conexión fallida");
$stmt = $mysqli->prepare("SELECT password FROM users WHERE username = ?");
$stmt->bind_param("s", $_POST['username']);
$stmt->execute();
$stmt->bind_result($hashedPassword);
if ($stmt->fetch() && password_verify($_POST['password'], $hashedPassword)) {
echo "Inicio de sesión correcto";
} else {
echo "Credenciales incorrectas";
}
$stmt->close();
$mysqli->close();phpAlém disso, nunca se devem guardar as palavras-passe diretamente na base de dados nem consultá-las em texto simples. Em vez disso, utilize um método de hash seguro, como password_hash(), combinado com password_verify(), para proteger adequadamente as credenciais. Uma implementação segura poderia ser assim:
$mysqli = new mysqli("localhost", "usuario", "contraseña", "base_de_datos");
$stmt = $mysqli->prepare("SELECT password FROM users WHERE username = ?");
$stmt->bind_param("s", $_POST['username']);
$stmt->execute();
$result = $stmt->get_result();
$row = $result->fetch_assoc();
if ($row && password_verify($_POST['password'], $row['password'])) {
echo "¡Inicio de sesión realizado con éxito!";
} else {
echo "Nombre de usuario o contraseña incorrectos.";
}phpBobby Tables: a injeção SQL explicada com humor sob a forma de banda desenhada
No site bobby-tables.com, aborda-se o tema das entradas de utilizador inseguras em bases de dados através de uma banda desenhada online do xkcd. A banda desenhada mostra uma mãe que recebe uma chamada da escola do seu filho, carinhosamente apelidado de «o pequeno Bobby Tables». Quando lhe perguntam se o filho se chama realmente Robert'); DROP TABLE Students;--, ela responde que sim, e então explicam-lhe o motivo da chamada: ao tentar criar um registo para o Robert na base de dados dos alunos, todo o conteúdo da tabela foi apagado. A mãe, longe de pedir desculpa, limita-se a expressar a esperança de que a escola tenha aprendido a lição e que, no futuro, verifique as entradas introduzidas na base de dados.
A banda desenhada ilustra de forma clara e divertida as consequências potencialmente desastrosas de não validar as entradas do utilizador em sistemas que utilizam bases de dados.