As injeções SQL re­pre­sen­tam uma grande ameaça para os modelos de bases de dados re­la­ci­o­nais e para a in­for­ma­ção que estas armazenam. Por isso, é fun­da­men­tal dispor de uma proteção completa contra estes acessos externos não au­to­ri­za­dos que se apro­vei­tam de vul­ne­ra­bi­li­da­des de segurança.

O que é uma injeção SQL?

Uma injeção SQL (do inglês SQL injection) define-se como a ex­plo­ra­ção de uma vul­ne­ra­bi­li­dade em bases de dados re­la­ci­o­nais que utilizam a linguagem de consulta SQL. O atacante aproveita-se das entradas do uti­li­za­dor que não são de­vi­da­mente filtradas e que contêm me­ta­ca­rac­te­res, como o traço duplo, as aspas ou o ponto e vírgula. Estes ca­rac­te­res re­pre­sen­tam funções especiais para o in­ter­pre­ta­dor de SQL e permitem a in­fluên­cia externa sobre as ins­tru­ções exe­cu­ta­das. É comum que as injeções SQL sejam re­a­li­za­das em conjunto com programas PGP e ASP, que dependem de in­ter­fa­ces mais antigas. Nestas, algumas das entradas não possuem o mas­ca­ra­mento correto, tornando-se assim o alvo perfeito para estes ataques.

Através do uso in­ten­ci­o­nal de ca­rac­te­res especiais, um uti­li­za­dor não au­to­ri­zado pode injetar comandos SQL adi­ci­o­nais 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, con­se­quen­te­mente, ao servidor de bases de dados na sua to­ta­li­dade.

Exemplos de injeções SQL: é assim que funcionam os ataques a bases de dados

Uma vez que os ser­vi­do­res de bases de dados podem ser fa­cil­mente lo­ca­li­za­dos, da mesma forma que é fácil executar ataques de injeção SQL, este método tornou-se ra­pi­da­mente um dos pre­fe­ri­dos dos ci­ber­cri­mi­no­sos em todo o mundo. Estes atuam com di­fe­ren­tes padrões de ataque e apro­vei­tam-se, sobretudo, das vul­ne­ra­bi­li­da­des tra­di­ci­o­nais e atuais das apli­ca­ções en­vol­vi­das no processo de gestão de dados. A seguir, e para explicar como funcionam estes ataques, apre­sen­tam-se, a título de exemplo, os dois métodos mais comuns.

Exemplo 1: acesso através de uma entrada do uti­li­za­dor mal en­mas­ca­rada

Nor­mal­mente, antes de um uti­li­za­dor poder aceder a uma base de dados, é ne­ces­sá­rio que se au­ten­ti­que. Para esse efeito, existem scripts que, por exemplo, criam um for­mu­lá­rio de início de sessão composto por um nome de uti­li­za­dor e uma palavra-passe. Os uti­li­za­do­res preenchem o for­mu­lá­rio e, em seguida, o script verifica se existem entradas cor­res­pon­den­tes na base de dados. Por pre­de­fi­ni­çã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 (pseu­do­có­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)
python

Um atacante tem agora a pos­si­bi­li­dade de manipular in­ten­ci­o­nal­mente o campo da palavra-passe através de uma injeção SQL, in­tro­du­zindo, por exemplo, password' OR 1='1, o que re­sul­ta­ria na seguinte consulta SQL:

sql = "SELECT id FROM users WHERE username='' AND password='password' OR 1='1'"
python

Assim, o atacante obtém acesso ao registo completo de uti­li­za­do­res na base de dados, uma vez que a palavra-passe é sempre válida (1=‘1’). Agora, pode registar-se como ad­mi­nis­tra­dor e efetuar al­te­ra­ções nas entradas. O campo do nome de uti­li­za­dor também pode ser ma­ni­pu­lado da mesma forma.

Exemplo 2: es­pi­o­na­gem de dados através da ma­ni­pu­la­ção de iden­ti­fi­ca­do­res

Solicitar in­for­ma­ções de uma base de dados através de um iden­ti­fi­ca­dor é um método prático e comum, bem como uma potencial porta de entrada para um ataque de injeção SQL. Por exemplo, o iden­ti­fi­ca­dor trans­mi­tido numa URL permite ao servidor web iden­ti­fi­car quais as in­for­ma­çõ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);
    }
?>
php

A URL esperada tem o formato .../script.php?id=22. Nesse caso, seria con­sul­tado o registo da tabela com o ID «22». No entanto, se uma pessoa externa tiver a pos­si­bi­li­dade de manipular essa URL e enviar ao servidor web a so­li­ci­ta­ção .../script.php?id=22+OR+1=1, a consulta re­sul­tante fará com que todos os dados da tabela sejam lidos:

SELECT * FROM tabla WHERE id=22 OR 1=1;
sql

Como é que os ci­ber­cri­mi­no­sos iden­ti­fi­cam as bases de dados vul­ne­rá­veis?

Em princípio, qualquer página web ou aplicação que utilize bases de dados SQL sem ins­tru­ções pre­pa­ra­das (prepared sta­te­ments) ou outros me­ca­nis­mos de proteção pode estar vul­ne­rá­vel a injeções SQL. As vul­ne­ra­bi­li­da­des des­co­ber­tas não per­ma­ne­cem 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 vul­ne­rá­veis através de pesquisas no Google. Se uma página devolver mensagens de erro SQL de­ta­lha­das, os ci­ber­cri­mi­no­sos podem apro­vei­tar essa in­for­ma­çã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 iden­ti­fi­ca­dor da URL faz com que uma página web vul­ne­rá­vel exiba uma mensagem de erro como esta:

Query failed: You have an error in your SQL Syntax …

Em português: «Erro na so­li­ci­ta­ção: a sintaxe do seu SQL está incorreta…». Com métodos se­me­lhan­tes, 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 uti­li­za­dor e palavras-passe. Além disso, existem fer­ra­men­tas que permitem au­to­ma­ti­zar tanto a pesquisa como as injeções SQL sub­se­quen­tes.

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, é ne­ces­sá­rio fa­mi­li­a­ri­zar-se com todos os com­po­nen­tes en­vol­vi­dos: o servidor, as apli­ca­ções in­di­vi­du­ais e o sistema de gestão de bases de dados.

Passo 1: mo­ni­to­ri­zar as al­te­ra­ções au­to­má­ti­cas nas apli­ca­ções

Ao processar entradas pro­ve­ni­en­tes de apli­ca­ções externas ou in­te­gra­das, é fun­da­men­tal validar e filtrar os valores enviados para evitar ataques de injeção SQL.

1. Verificar os tipos de dados

Cada dado in­tro­du­zido deve cor­res­pon­der 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");
}
php

Devem ser aplicadas ve­ri­fi­ca­ções se­me­lhan­tes a cadeias de texto, datas ou outros formatos es­pe­cí­fi­cos.

2. Filtrar ca­rac­te­res especiais

Os ca­rac­te­res especiais podem causar vul­ne­ra­bi­li­da­des, es­pe­ci­al­mente 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 de­ta­lha­das

Não devem ser apre­sen­ta­das mensagens de erro que revelem in­for­ma­ções técnicas sobre a base de dados ou o sistema. Em vez disso, é pre­fe­rí­vel apre­sen­tar 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.");
php

4. Utilizar ins­tru­ções pre­pa­ra­das (prepared sta­te­ments)

Uma forma segura de evitar injeções SQL é através da uti­li­za­ção de consultas pre­pa­ra­das (ou prepared sta­te­ments), como já foi referido. Este método separa as ins­tru­ções SQL dos pa­râ­me­tros, 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();
php

O sistema de gestão de bases de dados encarrega-se au­to­ma­ti­ca­mente de processar os dados de forma segura.

Passo 2: garantir uma proteção completa ao servidor

Lo­gi­ca­mente, a segurança do servidor no qual o sistema de gestão de bases de dados é executado de­sem­pe­nha um papel fun­da­men­tal 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 apli­ca­ções e os serviços que sejam realmente re­le­van­tes para a base de dados.
  • Elimine todas as contas de uti­li­za­dor que não sejam ne­ces­sá­rias.
  • Cer­ti­fi­que-se de instalar todas as atu­a­li­za­ções re­le­van­tes para o sistema e o programa.
  • Aplique o princípio do pri­vi­lé­gio mínimo para garantir que os uti­li­za­do­res e serviços tenham apenas as per­mis­sões es­tri­ta­mente ne­ces­sá­rias.

De acordo com os re­qui­si­tos de segurança do seu projeto web, convém con­si­de­rar também medidas de proteção adi­ci­o­nais:

  • Sistemas de deteção de intrusões (IDS) e sistemas de prevenção de intrusões (IPS): estes sistemas utilizam di­fe­ren­tes métodos de deteção para iden­ti­fi­car ataques ao servidor numa fase inicial, emitir alertas e, no caso dos IPS, aplicar au­to­ma­ti­ca­mente medidas de resposta.
  • Ap­pli­ca­tion Layer Gateway (ALG): um gateway de camada de aplicação, ou ALG, mo­ni­to­riza e filtra o tráfego de dados entre as apli­ca­ções e os na­ve­ga­do­res web di­re­ta­mente na camada de aplicação.
  • Firewall de apli­ca­ções web (WAF): um WAF protege es­pe­ci­fi­ca­mente as apli­ca­ções web contra injeções SQL e Cross Site Scripting (XSS), blo­que­ando ou neu­tra­li­zando pedidos suspeitos.
  • Abordagem Zero Trust: esta abordagem de segurança moderna garante que todo o acesso seja ve­ri­fi­cado e validado, in­de­pen­den­te­mente da sua origem, antes de ser au­to­ri­zado.
  • Regras de firewall e seg­men­ta­ção de rede: estas medidas são fun­da­men­tais para reduzir a su­per­fí­cie de ataque a longo prazo.
  • Au­di­to­rias de segurança de TI pe­rió­di­cas e testes de pe­ne­tra­ção: estas ações ajudam a iden­ti­fi­car e corrigir vul­ne­ra­bi­li­da­des antes que possam ser ex­plo­ra­das.

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 ir­re­le­van­tes e ser atu­a­li­zada re­gu­lar­mente. Para tal, é re­co­men­dá­vel desativar todos os serviços e contas de uti­li­za­dor inativos, bem como eliminar todas as rotinas ar­ma­ze­na­das que não sejam ne­ces­sá­rias. Configure uma conta de base de dados destinada ex­clu­si­va­mente 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();
php

Além disso, nunca se devem guardar as palavras-passe di­re­ta­mente 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 ade­qua­da­mente as cre­den­ci­ais. Uma im­ple­men­ta­çã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.";
}
php

Bobby 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 uti­li­za­dor 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, ca­ri­nho­sa­mente 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 in­tro­du­zi­das na base de dados.

A banda desenhada ilustra de forma clara e divertida as con­sequên­cias po­ten­ci­al­mente de­sas­tro­sas de não validar as entradas do uti­li­za­dor em sistemas que utilizam bases de dados.

Ir para o menu principal