Injeção de SQL
Muitos desenvolvedores web não sabem como consultas SQL podem ser manipuladas
e presumem que uma consulta de SQL é um comando confiável. Isso significa que consultas
SQL são capazes de passar indetectadas por controles de acesso, portanto desviando
da autenticação padrão e de checagens de autorização, e algumas vezes consultas SQL podem
permitir acesso à comandos no nível do sistema operacional do servidor.
A injeção direta de comandos SQL é uma técnica onde um atacante cria ou
altera comandos SQL existentes para expor dados escondidos, ou sobrescrever
dados valiosos, ou ainda executar comandos de sistema perigosos no servidor.
Isso é possível se a aplicação pegar a entrada do usuário e combinar
com parâmetros estáticos para montar uma consulta SQL. Os exemplos
a seguir são baseados em histórias verdadeiras, infelizmente.
Devido à falta de validação de entrada e à conexão ao banco de dados
usando o super-usuário ou um usuário que pode criar usuário, o atacante
pode criar um super-usuário no seu banco de dados.
Exemplo #1
Dividinto o result set em páginas ... e criando super-usuários
(PostgreSQL)
<?php
$offset = $argv[0]; // Cuidado, sem validação de entrada!
$query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";
$result = pg_query($conn, $query);
?>
Usuários normais clicam nos links 'próxima' e 'anterior' onde
$offset
é codificado na
URL. O script espera que o valor de
$offset seja um número decimal. No entanto, e se alguém tentar
invadir acrescentando a forma codificada por
urlencode() da
URL seguinte:
Se isso acontecesse, então o script daria de presente acesso de super-usuário ao
atacante. Perceba que
0;
é para fornecer uma deslocamento válido
para a consulta original e terminá-la.
Nota:
É 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. Uso de campos de senha criptografados é
fortemente incentivado.
Exemplo #2
Listando artigos ... e algumas senhas (qualquer banco de dados)
<?php
$query = "SELECT id, name, inserted, size FROM products
WHERE size = '$size'
ORDER BY $order LIMIT $limit, $offset;";
$result = odbc_exec($conn, $query);
?>
A parte estática da consulta pode ser combinada com outro comando
SELECT
que revela todas as senhas:
Se essa consulta (brincando com
'
e
--
) fosse atribuída para uma das variáveis usadas em
$query, a consulta tola seria acordada.
Comandos de UPDATE também são suscetíveis a ataques. Essas consultas também
são ameaçadas por cortes e acréscimos de uma nova consulta. Mas o
atacante tem que lidar com a cláusula SET
. Nesse caso
ele precisa estar de posse de alguma informação sobre o esquema para manipular a
consulta com sucesso. Isso pode ser conseguido examinando os nomes das variáveis do
formulário, ou simplesmente por força bruta. Não existem muitas convenções para
campos guardando senhas ou nomes de usuários.
Exemplo #3
De recuperando uma senha ... para ganhando mais privilégios (qualquer banco de dados)
<?php
$query = "UPDATE usertable SET pwd='$pwd' WHERE uid='$uid';";
?>
Mas se um usuário malicioso envia o valor
' or uid like'%admin%'; --
para
$uid para
mudar a senha do administrador, ou simplesmente configura
$pwd para
"hehehe', admin='yes', trusted=100 "
(com um espaço
sobrando) para ganhar mais privilégios. Então, a consulta ficará retorcida:
Um exemplo assustador de como comandos do sistema operacional podem ser acessados
em alguns bancos de dados.
Exemplo #4 Atacando o sistema operacional do servidor (MSSQL Server)
<?php
$query = "SELECT * FROM products WHERE id LIKE '%$prod%'";
$result = mssql_query($query);
?>
Se o atacante enviar o valor
a%' exec master..xp_cmdshell 'net user test testpass /ADD' --
para
$prod, então
$query terá o valor:
O MSSQL Server executa comandos SQL em um lote incluindo um comando
para adicionar um novo usuário para o banco de dados de contas locais. Se essa aplicação
estiver sendo executada como
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.
Nota:
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 outa maneira.
Imagem em cortesia de
» xkcd
Técnicas para Evitar Ataques
Embora seja óbvio que um invasor deve possuir pelo menos alguns
conhecimento da arquitetura de banco de dados, a fim de conduzir um sucesso
ataque, a obtenção dessas informações costuma ser muito simples. Por exemplo,
se o banco de dados é parte de um código aberto ou outro pacote de software com uma instalação padrão
disponível publicamente , esta informação é
completamente aberta e disponível. Essas informações também podem ser divulgadas
por código-fonte fechado - mesmo se for codificado, ofuscado ou compilado -
e até mesmo por seu próprio código, por meio da exibição de mensagens de erro.
Outros métodos incluem o usuário de nomes comuns de tabelas e colunas. Para
exemplo, um formulário de login que usa uma tabela de 'usuários' com nomes de colunas
'id', 'username' e 'password'.
Esses ataques se baseam principalmente em explorar falhas no código escrito
sem se preocupar com segurança. Nunca confie em nenhum tipo de entrada, especialmente
aquela que vem do lado do cliente, mesmo que venha de um combobox,
um campo de entrada escondido (hidden) ou um cookie. O primeiro exemplo mostra
como uma consulta inocente pode causar desastres.
-
Nunca conecte ao banco de dados como um super-usuário ou como o dono do banco
de dados. Use sempre usuários personalidados com privilégios bem limitados.
-
Use declarações preparadas com variáveis associadas. Eles são fornecidos
pelo PDO,
por MySQLi
e por outras bibliotecas.
-
Verifique se a entrada fornecida tem o tipo de dados esperado. O PHP tem
uma ampla gama de funções de validação de entrada, desde as mais simples
encontrado em Funções de variável e
em Funções de tipo de caractere
(por exemplo, is_numeric(), ctype_digit()
respectivamente) e até mesmo com suporte de
Expressões regulares compatíveis com Perl
-
Se a aplicação espera por entradas numéricas, considere verificar os dados
com a função is_numeric(), ou silenciosamente mudar o seu
tipo usando settype(), ou usar a representação
númerica usando a função sprintf().
Exemplo #5 Uma maneira mais segura para compor consultas de paginação
<?php
settype($offset, 'integer');
$query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";
// por favor perceba o %d na string de formato, usando %s seria inútil
$query = sprintf("SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET %d;",
$offset);
?>
-
Se a camada de banco de dados não suporta variáveis de ligação, então
cite cada valor não numérico fornecido pelo usuário que é passado para o
banco de dados com a função de escape de string específica do banco de dados (por exemplo
mysql_real_escape_string(),
sqlite_escape_string(), etc.).
Funções genéricas como addlashes() são úteis apenas
em um ambiente muito específico (por exemplo, MySQL em um caractere de byte único
definido com NO_BACKSLASH_ESCAPES desativado) assim é
melhor evitá-los.
-
Não imprima qualquer informação específica do banco de dados, especialmente
sobre o esquema, custe o que custar. Veja também Relatório de Erros e Funções de Tratamento e Relatório de Erros.
-
Você pode usar stored procedures e cursores previamente definidos para abstrair
acesso aos dados para que os usuários não acessem tabelas ou views diretamente, mas
essa solução pode ter outros impactos.
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 previnir 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
atráves da informação que ele contém. Mais detalhes geralmente é melhor que menos.