Injeção de SQL
A injeção de SQL é uma técnica onde o agressor explora falhas no código da aplicação responsável em criar e povoar instruções SQL. O agressor pode assim obter acesso privilegiado a partes da aplicação, extrair todos os dados do banco de dados, alterar os dados, e até mesmo executar comandos perigosos em nível do sistema onde o banco de dados roda. A falha ocorre quando desenvolvedores concatenam ou interpolam dados arbitrários em instruções SQL.
Example #1 Dividindo o result set em páginas ... e criando super-usuários (PostgreSQL)
No exemplo a seguir, dados de usuário são diretamente interpolados na instrução SQL, permitindo ao agressor obter uma conta de superusuário no banco de dados.
<?php
$offset = $_GET['offset']; // Usando os dados sem validação!
$query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";
$result = pg_query($conn, $query);
?>
0; insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd) select 'crack', usesysid, 't','t','crack' from pg_shadow where usename='postgres'; --
0;
é para fornecer uma deslocamento válido
para a consulta original e terminá-la.
Note:
É uma técnica comum forçar o avaliador de SQL ignorar o resto da consulta escrita pelo desenvolvedor com
--
, que é o sinal de comentário no SQL.
Uma maneira de ganhar senhas é desviar suas páginas de resultado de busca.
A única coisa que o atacante precisa fazer é ver se alguma variável enviada
é usada em um comando SQL que não é tratado corretamente. Esses filtros podem ser
configurados de forma a personalizar cláusulas WHERE, ORDER BY,
LIMIT
e OFFSET
em cláusulas SELECT
Se seu banco de dados suporta o construtor UNION
,
o atacante pode tentar adicionar uma consulta inteira à consulta original para
listar senhas de uma tabela arbitrária. É altamente recomendável gravar apenas
hashs criptográficos das senhas, ao invés de gravar a senha.
Example #2 Listando artigos ... e algumas senhas (qualquer banco de dados)
<?php
$query = "SELECT id, name, inserted, size FROM products
WHERE size = '$size'";
$result = odbc_exec($conn, $query);
?>
SELECT
que revela todas as senhas:
' union select '1', concat(uname||'-'||passwd) as name, '1971-01-01', '0' from usertable; --
Instruções UPDATE
e INSERT
também podem
ser abusados em ataques.
Example #3 De recuperando uma senha ... para ganhando mais privilégios (qualquer banco de dados)
<?php
$query = "UPDATE usertable SET pwd='$pwd' WHERE uid='$uid';";
?>
' or uid like'%admin%
para $uid para
mudar a senha do administrador, ou simplesmente configura $pwd para
hehehe', trusted=100, admin='yes
(com um espaço
sobrando) para ganhar mais privilégios. Então, a consulta ficará retorcida:
<?php
// $uid: ' or uid like '%admin%
$query = "UPDATE usertable SET pwd='...' WHERE uid='' or uid like '%admin%';";
// $pwd: hehehe', trusted=100, admin='yes
$query = "UPDATE usertable SET pwd='hehehe', trusted=100, admin='yes' WHERE
...;";
?>
Pode parecer que o agressor precisa saber alguma coisa da arquitetura do banco para conduzir um ataque efetivo, mas obter esse tipo de informação é geralmente bem simples. Por exemplo, o código pode ser parte de um sistema open source e publicamente disponível. Esse tipo de informação pode também pode ser obtido em sistemas de código fechado -- mesmo no caso dele estar ofuscado ou compilado -- e mesmo através do seu próprio código, através de mensagens de erro. Outros métodos incluem o uso de nomes típicos de tabelas e colunas. Por exemplo, um formulário de login normalmente utiliza tabelas chamadas 'user' com colunas chamadas 'id', 'username', and 'password'.
Example #4 Atacando o sistema de um banco de dados (MSSQL Server)
Um exemplo assustador de como comandos do sistema operacional podem ser acessados em alguns bancos de dados.
<?php
$query = "SELECT * FROM products WHERE id LIKE '%$prod%'";
$result = mssql_query($query);
?>
a%' exec master..xp_cmdshell 'net user test testpass /ADD' --
para $prod, então $query terá o valor:
<?php
$query = "SELECT * FROM products
WHERE id LIKE '%a%'
exec master..xp_cmdshell 'net user test testpass /ADD'--";
$result = mssql_query($query);
?>
sa
e o serviço MSSQLSERVER estivesse
sendo executado com privilégios suficientes, o atacante teria agora uma
conta com a qual poderia acessar essa máquina.
Note:
Alguns dos exemplos acima estão ligados a bancos específicos. Isso não significa que um ataque similar é impossível contra outros produtos. Seu servidor de banco de dados pode ter uma vulnerabilidade similar de outra maneira.
Técnicas para evitar ataques
A maneira recomendada de evitar ataques de injeção de SQL é informar todos os
dados em instruções preparadas. Usar instruções parametrizadas não é o suficiente
para evitar SQL injection, mas é a maneira mais rápida e segura de fornecer dados
a instruções SQL. Todo os dados dinâmicos em WHERE
,
SET
, e VALUES
precisam ser
substituídos por âncoras. Os dados em si serão informados durante a
execução, e serão enviados separadamente do comando SQL.
Informar dados via parâmetros deve ser utilizado apenas para dados. Outras partes dinâmicas de uma instrução SQL precisa ser filtrada por uma lista prévia e conhecida de valores válidos.
Example #5 Evitando SQL injection ao utilizar instruções preparadas PDO
<?php
// A parte SQL dinâmica precisa é validada a partir de dados prévios
$sortingOrder = $_GET['sortingOrder'] === 'DESC' ? 'DESC' : 'ASC';
$productId = $_GET['productId'];
// O SQL é preparado utilizando âncoras
$stmt = $pdo->prepare("SELECT * FROM products WHERE id LIKE ? ORDER BY price {$sortingOrder}");
// O valor é informado, incluindo caracteres curinga
$stmt->execute(["%{$productId}%"]);
?>
Instruções preparadas são fornecidos no PDO, no MySQLi, e por outras bibliotecas de bancos de dados.
Ataques de injeção de SQL são principalmente baseados na exploração de código que não é escrito pensando em segurança. Nunca confie em nenhum dado enviado pelo usuário, e menos ainda em dados enviados pelo navegador, mesmo que o dado venha de um option box ou um campo hidden, nem mesmo cookies. O primeiro exemplo mostra como uma instrução SQL muito simples pode causar um dano desastroso.
Uma estratégia de defesa envolve várias boas práticas de codificação:
- Nunca se conecte no banco de dados utilizando um usuário administrador ou dono dos objetos do banco. Sempre utilize usuários com privilégios mínimos.
- Sempre verifique se o dado enviado tem o tipo esperado. O PHP possui várias funções de validação de dados, de coisas simples como as encontradas em funções de variável e em funções de string (por exemplo, is_numeric(), ctype_digit()) a coisas mais avançadas como suporte a expressões regulares compatíveis com Perl.
- Se a aplicação espera dados numéricos, considere verificar os dados com ctype_digit(), ou modificar os dados utilizando settype(), ou ainda reformatar o dado com sprintf().
- Se o banco de dados não suportar enviar dados por parâmetros, então é necessário escapar todos os dados de usuário não numéricos, passando o dado para funções específicas de escape do banco (por exemplo mysql_real_escape_string(), sqlite_escape_string(), etc). Funções genéricas como addslashes() são úteis apenas em contextos específicos (por exemplo, no MySQL é possível modificar o comportamento das aspas com NO_BACKSLASH_ESCAPES), então o escape específico é necessário.
- Nunca imprimi nenhum dado ou erros específico do banco de dados, especialmente dados referentes a schema. Veja também exibição de erros e funções de manipulação e log de erros.
Além disso, você ganha em relatar consultas ou dentro do script ou no próprio banco de dados, se esse suportar. Obviamente, o relatório é incapaz de prevenir qualquer tentativa danosa, mas pode ser útil para ajudar a rastrear qual aplicação foi atacada. O relatório não é útil em si, mas através da informação que ele contém. Mais detalhes geralmente é melhor que menos.